@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
@@ -1,36 +1,42 @@
1
- import { useBreadcrumbs } from '../chunk-MG3NF7QL.js';
2
1
  import '../chunk-SMJLS23U.js';
3
- import { NotificationList } from '../chunk-ZY4MWZW2.js';
4
- export { ActivityCard, ActivityFilters as ActivityFiltersBar, ActivityTable, BusinessImpactCard, CostBreakdownCard, CostByModelTable, CostMetricsCard, ErrorAnalysisCard, ErrorBreakdownTable, ExecutionBreakdownTable, ExecutionHealthCard, ExecutionLogsFilters as ExecutionLogsFilterBar, ExecutionLogsTable, NotificationItem, NotificationList, monitoringManifest } from '../chunk-ZY4MWZW2.js';
5
- export { CreateCredentialModal, CredentialList, CredentialSettings, MembershipFeaturePanel, MembershipStatusBadge, OAuthConnectModal, OrganizationMembershipsList, WebhookUrlDisplayModal, settingsManifest } from '../chunk-PEATQEEP.js';
2
+ import { useBreadcrumbs } from '../chunk-MG3NF7QL.js';
3
+ export { HealthStatusCard, MilestoneTimeline, ProjectDetailPage, ProjectsListPage, ProjectsSidebar, ProjectsSidebarMiddle, ProjectsSidebarTop, TaskCard, calculateProgress, deliveryManifest, formatStatusLabel, milestoneStatusColors, noteTypeColors, projectStatusColors, taskStatusColors, taskTypeColors } from '../chunk-WSL5MNAI.js';
4
+ export { LEAD_GEN_ROUTE_LINKS, LIST_TEMPLATE_OPTIONS, LeadGenCompaniesPage, LeadGenContactsPage, LeadGenDeliverabilityPage, LeadGenListDetailPage, LeadGenListsPage, LeadGenOverviewPage, LeadGenRouteShell, LeadGenSidebar, LeadGenSidebarMiddle, LeadGenSidebarTop, buildListConfig, getEnrichmentColor, getStatusColor, leadGenManifest, useDeleteLists } from '../chunk-3ONP2CEB.js';
5
+ export { ActionModal, AgentDefinitionDisplay, AgentExecutionLogs, BaseExecutionLogs, BaseExecutionLogsHeader, BaseExecutionLogsStates, CheckpointGroup, CollapsibleJsonSection, CommandQueueSidebar, CommandQueueSidebarMiddle, CommandQueueSidebarTop, CommandQueueTaskRow, ConfigCard, ContentSections, ContextUsageBadge, ContractDisplay, ExecutionErrorSection, FormFieldRenderer, LogEntry, LogGroup, NewKnowledgeMapEdge, NewKnowledgeMapGraph, NewKnowledgeMapNode, OperationsSidebar, OperationsSidebarMiddle, OperationsSidebarTop, ResourceDefinitionSection, ResourceErrorState, ResourceFilter, ResourceHeader, ResourceNotFoundState, SessionMemory, ToolsListDisplay, WorkflowDefinitionDisplay, WorkflowExecutionLogs, getExecutionStatusConfig, getIcon, getLogLevelConfig, iconMap, operationsManifest, useNewKnowledgeMapLayout } from '../chunk-KRTZTBVP.js';
6
+ import '../chunk-ROSMICXG.js';
7
+ import { SubshellLoader, CollapsibleSidebarGroup } from '../chunk-IDACMRGQ.js';
8
+ import { NotificationList } from '../chunk-5XGBMKUY.js';
9
+ export { ActivityCard, ActivityFilters as ActivityFiltersBar, ActivityTable, BusinessImpactCard, CostBreakdownCard, CostByModelTable, CostMetricsCard, ErrorAnalysisCard, ErrorBreakdownTable, ExecutionBreakdownTable, ExecutionHealthCard, ExecutionLogsFilters as ExecutionLogsFilterBar, ExecutionLogsTable, NotificationItem, NotificationList, monitoringManifest } from '../chunk-5XGBMKUY.js';
10
+ export { ResourceHealthPanel } from '../chunk-ZG7MLOBE.js';
11
+ export { SEOSidebar, SEOSidebarMiddle, SEOSidebarTop, seoManifest } from '../chunk-J5KWNRSD.js';
12
+ export { CreateCredentialModal, CredentialList, CredentialSettings, MembershipFeaturePanel, MembershipStatusBadge, OAuthConnectModal, OrganizationMembershipsList, WebhookUrlDisplayModal, settingsManifest } from '../chunk-WN764MR7.js';
13
+ export { ActivityFeedWidget, CrmOverview, CrmSidebar, CrmSidebarMiddle, CrmSidebarTop, DealDetailPage, DealsListPage, MetricsStrip, MyTasksPanel, PIPELINE_FUNNEL_ORDER, PipelineFunnelWidget, QuickCreateActions, SAVED_VIEW_PRESETS, SavedViewsPanel, TasksDueWidget, crmManifest, useCrmPipelineSummary, useCrmQuickMetrics, useRecentCrmActivity } from '../chunk-2DZACNOX.js';
14
+ export { SortableHeader, TableSelectionToolbar } from '../chunk-TUMSNGTX.js';
15
+ import { PageContainer } from '../chunk-BZZCNLT6.js';
16
+ import { SubshellNavItem } from '../chunk-27COZ5AH.js';
6
17
  import { FilterBar } from '../chunk-PDHTXPSF.js';
7
18
  export { FilterBar } from '../chunk-PDHTXPSF.js';
8
- import { ResourceExecuteDialog } from '../chunk-NKV5MEWQ.js';
9
- export { ActionModal, AgentDefinitionDisplay, AgentExecutionLogs, BaseExecutionLogs, BaseExecutionLogsHeader, BaseExecutionLogsStates, CheckpointGroup, CollapsibleJsonSection, CommandQueueSidebar, CommandQueueSidebarMiddle, CommandQueueSidebarTop, CommandQueueTaskRow, ConfigCard, ContentSections, ContextUsageBadge, ContractDisplay, ExecutionErrorSection, FormFieldRenderer, LogEntry, LogGroup, NewKnowledgeMapEdge, NewKnowledgeMapGraph, NewKnowledgeMapNode, OperationsSidebar, OperationsSidebarMiddle, OperationsSidebarTop, ResourceDefinitionSection, ResourceErrorState, ResourceFilter, ResourceHeader, ResourceNotFoundState, SessionMemory, ToolsListDisplay, WorkflowDefinitionDisplay, WorkflowExecutionLogs, getExecutionStatusConfig, getIcon, getLogLevelConfig, iconMap, operationsManifest, useNewKnowledgeMapLayout } from '../chunk-NKV5MEWQ.js';
10
- import '../chunk-ROSMICXG.js';
11
- import { SubshellLoader, PageContainer, SubshellSidebarSection, SubshellNavItem, CollapsibleSidebarGroup } from '../chunk-OCP2MBTY.js';
12
- export { ResourceHealthPanel } from '../chunk-IWFIKQR5.js';
13
19
  import { CustomModal } from '../chunk-GBMNCNHX.js';
14
20
  export { ConfirmationInputModal, ConfirmationModal, CustomModal } from '../chunk-GBMNCNHX.js';
15
- import { BaseNode, useGraphTheme, BaseEdge, GraphBackground, GraphLegend, GraphFitViewButton } from '../chunk-SWIAK47F.js';
16
- export { AgentExecutionTimeline, AgentExecutionVisualizer, AgentIterationEdge, AgentIterationNode, BaseEdge, BaseNode, EmptyVisualizer, ExecutionStats, ExecutionStatusBadge, GraphBackground, GraphContainer, GraphFitViewButton, GraphFitViewHandler, GraphLegend, TimelineAxis, TimelineBar, TimelineContainer, TimelineRow, UnifiedWorkflowEdge, UnifiedWorkflowGraph, UnifiedWorkflowNode, VisualizerContainer, WorkflowExecutionTimeline, dashboardManifest, getGraphBackgroundStyles, useGraphBackgroundStyles, useGraphTheme } from '../chunk-SWIAK47F.js';
21
+ import { BaseNode, useGraphTheme, BaseEdge, GraphBackground, GraphLegend, GraphFitViewButton } from '../chunk-G3G2QEB6.js';
22
+ export { AgentExecutionTimeline, AgentExecutionVisualizer, AgentIterationEdge, AgentIterationNode, BaseEdge, BaseNode, EmptyVisualizer, ExecutionStats, ExecutionStatusBadge, GraphBackground, GraphContainer, GraphFitViewButton, GraphFitViewHandler, GraphLegend, TimelineAxis, TimelineBar, TimelineContainer, TimelineRow, UnifiedWorkflowEdge, UnifiedWorkflowGraph, UnifiedWorkflowNode, VisualizerContainer, WorkflowExecutionTimeline, dashboardManifest, getGraphBackgroundStyles, useGraphBackgroundStyles, useGraphTheme } from '../chunk-G3G2QEB6.js';
17
23
  export { ResourceHealthChart, getHealthColor } from '../chunk-LGKLC5MG.js';
18
24
  import '../chunk-KFICYU6S.js';
19
25
  import { AppShellLoader } from '../chunk-YEX4MQSY.js';
20
26
  import '../chunk-VNUOQQNY.js';
21
- import { useUpdateApiKey, useDeleteApiKey, useCreateApiKey, useListApiKeys, useActivateDeployment, useDeactivateDeployment, useDeleteDeployment, useListDeployments, useProjects, useProject, useProjectNotes, useDeleteProject as useDeleteProject$1, useUpdateMilestone, useCreateNote } from '../chunk-Q3FTQP2M.js';
22
- import { useCommandViewLayout, usePaginationState, useDeploymentDocs, useResources, useCreateSchedule, useListSchedules, usePauseSchedule, useResumeSchedule, useCancelSchedule, useDeleteSchedule, useDealNotes, useCreateDealNote, useDeals, useSyncDealStage, dealKeys, useDealTasksDue, useCreateDealTask, useDeleteDeal, useTableSort, sortData, useTableSelection, useDealDetail, showApiErrorNotification, acquisitionListKeys, showSuccessNotification, useListsTelemetry, useLists, useCreateList, useList, useListProgress, useListExecutions, useResourceDefinition, useCompanies, useDeleteCompanies, useContacts, useDeleteContacts, useDeleteProject, useMarkAllAsRead, useNotifications, showErrorNotification } from '../chunk-2XWEOJSX.js';
23
- export { showApiErrorNotification, showErrorNotification, showInfoNotification, showSuccessNotification, showWarningNotification } from '../chunk-2XWEOJSX.js';
24
- import '../chunk-LXHZYSMQ.js';
25
- import { Graph_module_css_default, useDirectedChainHighlighting, useNodeSelection, GRAPH_CONSTANTS } from '../chunk-F6RBK7NJ.js';
26
- export { Graph_module_css_default as graphStyles } from '../chunk-F6RBK7NJ.js';
27
27
  export { CONTAINER_CONSTANTS, SHARED_VIZ_CONSTANTS } from '../chunk-XA34RETF.js';
28
+ import { useUpdateApiKey, useDeleteApiKey, useCreateApiKey, useListApiKeys, useActivateDeployment, useDeactivateDeployment, useDeleteDeployment, useListDeployments } from '../chunk-PEZ4WOPF.js';
29
+ import { useCommandViewLayout, usePaginationState, useDeploymentDocs, useResources, useCreateSchedule, useListSchedules, usePauseSchedule, useResumeSchedule, useCancelSchedule, useDeleteSchedule, useDealNotes, useCreateDealNote, useDeals, useSyncDealStage, dealKeys, useMarkAllAsRead, useNotifications, showErrorNotification, showSuccessNotification, showApiErrorNotification } from '../chunk-IPRMGSCV.js';
30
+ export { showApiErrorNotification, showErrorNotification, showInfoNotification, showSuccessNotification, showWarningNotification } from '../chunk-IPRMGSCV.js';
31
+ import '../chunk-LXHZYSMQ.js';
32
+ import { Graph_module_css_default, useDirectedChainHighlighting, useNodeSelection, GRAPH_CONSTANTS } from '../chunk-22UVE3RA.js';
33
+ export { Graph_module_css_default as graphStyles } from '../chunk-22UVE3RA.js';
28
34
  import '../chunk-JT7WDIZI.js';
29
35
  import '../chunk-47YILFON.js';
30
36
  import '../chunk-CYXZHBP4.js';
31
37
  import '../chunk-ISHNN42L.js';
32
38
  import { SubshellContainer, SubshellSidebar, SubshellRightSideContainer, SubshellContentContainer } from '../chunk-RX4UWZZR.js';
33
- import { ListSkeleton, EmptyState, PageTitleCaption, StatCard, CenteredErrorState, CardHeader, ActivityTimeline, StatusBadge } from '../chunk-Y3D3WFJG.js';
39
+ import { ListSkeleton, EmptyState, PageTitleCaption, StatCard, CenteredErrorState, CardHeader, ActivityTimeline } from '../chunk-Y3D3WFJG.js';
34
40
  export { APIErrorAlert, ActivityTimeline, CardHeader, CenteredErrorState, CollapsibleSection, ContextViewer, CustomSelector, DetailCardSkeleton, ElevasisLoader, EmptyState, FeatureUnavailableState, GlowDot, JsonViewer, ListSkeleton, PageNotFound, PageTitleCaption, ResourceCard, StatCard, StatCardSkeleton, StatsCardSkeleton, StatusBadge, TabCountBadge, TimeRangeSelector, TrendIndicator, catalogItemToResourceDefinition } from '../chunk-Y3D3WFJG.js';
35
41
  export { StyledMarkdown } from '../chunk-3KMDHCAR.js';
36
42
  export { NavigationButton } from '../chunk-NYBEU5TE.js';
@@ -42,20 +48,20 @@ import { useAppearance } from '../chunk-QJ2KCHKX.js';
42
48
  import '../chunk-DT3QYZVU.js';
43
49
  import '../chunk-SLVC5OJ2.js';
44
50
  import '../chunk-RNP5R5I3.js';
45
- import { getResourceIcon, getResourceColor, formatDateTime, PAGE_SIZE_DEFAULT, formatTimeAgo, formatDate } from '../chunk-IOKL7BKE.js';
51
+ import { getResourceIcon, getResourceColor, formatDateTime, PAGE_SIZE_DEFAULT, formatTimeAgo } from '../chunk-IOKL7BKE.js';
46
52
  import '../chunk-MTJ43R2E.js';
47
53
  import { useInitialization } from '../chunk-TUXTSEAF.js';
48
54
  import '../chunk-DD3CCMCZ.js';
49
55
  import { useElevasisServices } from '../chunk-QEPXAWE2.js';
50
56
  import '../chunk-BRJ3QZ4E.js';
51
57
  import { useRouterContext } from '../chunk-Q7DJKLEN.js';
52
- import { Stack, Group, ThemeIcon, Text, Badge, Box, Table, Button, Title, TextInput, Alert, Tooltip, ActionIcon, Paper, Code, CopyButton, SimpleGrid, Loader, Pagination, useMantineTheme, ScrollArea, Select, Center, Card, SegmentedControl, Switch, Textarea, Divider, Menu, Timeline, Tabs, Breadcrumbs as Breadcrumbs$1, Drawer, UnstyledButton, Modal, Checkbox, Anchor, Progress, RingProgress, Collapse, Popover, Indicator } from '@mantine/core';
53
- import { IconBrain, IconDatabase, IconMessage, IconAlertCircle, IconCircleX, IconCircleCheck, IconBolt, IconHandClick, IconClock, IconWebhook, IconExternalLink, IconAddressBook, IconBriefcase, IconTarget, IconChevronUp, IconChevronDown, IconSelector, IconTrash, IconPencil, IconKey, IconCalendar, IconAlertTriangle, IconExclamationMark, IconShieldLock, IconCheck, IconCopy, IconPlus, IconRocket, IconRefresh, IconPower, IconPlayerPlay, IconTag, IconBook2, IconFileOff, IconList, IconCalendarRepeat, IconCalendarEvent, IconCalendarTime, IconRobot, IconGitBranch, IconSettings, IconDotsVertical, IconPlayerPause, IconPlayerStop, IconCalendarDue, IconCalendarStats, IconCalendarOff, IconListCheck, IconTrophy, IconClockExclamation, IconUser, IconLayoutGrid, IconColumns, IconFileInvoice, IconChecklist, IconHistory, IconSearch, IconTargetArrow, IconArrowLeft, IconFileText, IconX, IconBuilding, IconMailCheck, IconArrowRight, IconQuestionMark, IconSparkles, IconClockHour4, IconBuildingFactory2, IconUsers, IconChartBar, IconTrendingUp, IconHeartbeat, IconFlag, IconInbox, IconLock, IconChevronRight, IconDownload, IconMessageCircle, IconBell, IconNotes, IconFolderOpen, IconFolder, IconCheckbox, IconMail, IconPhone, IconNote } from '@tabler/icons-react';
54
- import * as runtime from 'react/jsx-runtime';
55
- import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
58
+ import { Stack, Group, ThemeIcon, Text, Badge, Box, Title, TextInput, Alert, Button, Table, Tooltip, ActionIcon, Paper, Code, CopyButton, SimpleGrid, Loader, Pagination, useMantineTheme, ScrollArea, Select, Center, Card, SegmentedControl, Switch, Textarea, Divider, Menu, Timeline, Tabs, Breadcrumbs as Breadcrumbs$1, Drawer, Popover, Indicator } from '@mantine/core';
56
59
  import { useDisclosure } from '@mantine/hooks';
57
- import { useQueryClient, useQuery, useMutation } from '@tanstack/react-query';
60
+ import { IconBrain, IconDatabase, IconMessage, IconAlertCircle, IconCircleX, IconCircleCheck, IconBolt, IconHandClick, IconClock, IconWebhook, IconExternalLink, IconPencil, IconKey, IconCalendar, IconTrash, IconAlertTriangle, IconExclamationMark, IconShieldLock, IconCheck, IconCopy, IconPlus, IconRocket, IconRefresh, IconPower, IconPlayerPlay, IconTag, IconBook2, IconFileOff, IconList, IconCalendarRepeat, IconCalendarEvent, IconCalendarTime, IconRobot, IconGitBranch, IconSettings, IconDotsVertical, IconPlayerPause, IconPlayerStop, IconCalendarDue, IconCalendarStats, IconCalendarOff, IconListCheck, IconBell, IconFolderOpen, IconFolder, IconFileText } from '@tabler/icons-react';
61
+ import { useQueryClient, useQuery } from '@tanstack/react-query';
58
62
  import { memo, forwardRef, useMemo, useImperativeHandle, useState, useEffect, useCallback, useRef } from 'react';
63
+ import * as runtime from 'react/jsx-runtime';
64
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
59
65
  import { useForm } from '@mantine/form';
60
66
  import { useReactFlow, ReactFlow, ReactFlowProvider } from '@xyflow/react';
61
67
  import '@xyflow/react/dist/style.css';
@@ -65,36 +71,7 @@ import Placeholder from '@tiptap/extension-placeholder';
65
71
  import StarterKit from '@tiptap/starter-kit';
66
72
  import { Prism } from 'react-syntax-highlighter';
67
73
  import { oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism';
68
- import { useNavigate, Link as Link$1 } from '@tanstack/react-router';
69
74
 
70
- function SortableHeader({ column, children, sort, onToggle, style, w }) {
71
- const isActive = sort.column === column;
72
- return /* @__PURE__ */ jsx(Table.Th, { style: { ...style, cursor: "pointer", userSelect: "none" }, w, onClick: () => onToggle(column), children: /* @__PURE__ */ jsxs(Group, { gap: 4, wrap: "nowrap", children: [
73
- children,
74
- isActive ? sort.direction === "asc" ? /* @__PURE__ */ jsx(IconChevronUp, { size: 14 }) : /* @__PURE__ */ jsx(IconChevronDown, { size: 14 }) : /* @__PURE__ */ jsx(IconSelector, { size: 14, style: { opacity: 0.3 } })
75
- ] }) });
76
- }
77
- function TableSelectionToolbar({ selectedCount, onDelete, isDeleting }) {
78
- if (selectedCount === 0) return null;
79
- return /* @__PURE__ */ jsxs(Group, { gap: "sm", children: [
80
- /* @__PURE__ */ jsxs(Text, { size: "sm", fw: 600, c: "blue", children: [
81
- selectedCount,
82
- " selected"
83
- ] }),
84
- onDelete && /* @__PURE__ */ jsx(
85
- Button,
86
- {
87
- size: "sm",
88
- color: "red",
89
- variant: "light",
90
- leftSection: /* @__PURE__ */ jsx(IconTrash, { size: 16 }),
91
- loading: isDeleting,
92
- onClick: onDelete,
93
- children: "Delete"
94
- }
95
- )
96
- ] });
97
- }
98
75
  function EditApiKeyModal({ apiKey, onClose }) {
99
76
  const [name, setName] = useState("");
100
77
  const [nameError, setNameError] = useState(null);
@@ -1729,7 +1706,7 @@ function DeleteScheduleModal({ opened, onClose, onConfirm, schedule, isDeleting
1729
1706
  ] })
1730
1707
  ] }) });
1731
1708
  }
1732
- function getStatusColor(status) {
1709
+ function getStatusColor2(status) {
1733
1710
  switch (status) {
1734
1711
  case "active":
1735
1712
  return "green";
@@ -1741,7 +1718,7 @@ function getStatusColor(status) {
1741
1718
  return "gray";
1742
1719
  }
1743
1720
  }
1744
- function formatDate2(date) {
1721
+ function formatDate(date) {
1745
1722
  if (!date) return "N/A";
1746
1723
  const d = typeof date === "string" ? new Date(date) : date;
1747
1724
  return d.toLocaleString("en-US", {
@@ -1781,7 +1758,7 @@ function ScheduleConfigDetails({ schedule }) {
1781
1758
  ] }),
1782
1759
  config.endAt && /* @__PURE__ */ jsxs(Table.Tr, { children: [
1783
1760
  /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { c: "dimmed", children: "Ends" }) }),
1784
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { children: formatDate2(config.endAt) }) })
1761
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { children: formatDate(config.endAt) }) })
1785
1762
  ] }),
1786
1763
  /* @__PURE__ */ jsxs(Table.Tr, { children: [
1787
1764
  /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { c: "dimmed", children: "Overdue" }) }),
@@ -1803,7 +1780,7 @@ function ScheduleConfigDetails({ schedule }) {
1803
1780
  /* @__PURE__ */ jsxs(Table.Tr, { children: [
1804
1781
  /* @__PURE__ */ jsx(Table.Td, { w: 120, children: /* @__PURE__ */ jsx(Text, { c: "dimmed", children: "Anchor" }) }),
1805
1782
  /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsxs(Text, { children: [
1806
- formatDate2(config.anchorAt),
1783
+ formatDate(config.anchorAt),
1807
1784
  config.anchorLabel && ` (${config.anchorLabel})`
1808
1785
  ] }) })
1809
1786
  ] }),
@@ -1824,7 +1801,7 @@ function ScheduleConfigDetails({ schedule }) {
1824
1801
  ] }),
1825
1802
  /* @__PURE__ */ jsx(Stack, { gap: 4, children: config.items.map((item, i) => /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
1826
1803
  /* @__PURE__ */ jsx(Badge, { size: "xs", variant: "light", color: i === schedule.currentStep ? "blue" : "gray", children: i + 1 }),
1827
- /* @__PURE__ */ jsx(Text, { children: formatDate2(item.runAt) }),
1804
+ /* @__PURE__ */ jsx(Text, { children: formatDate(item.runAt) }),
1828
1805
  item.label && /* @__PURE__ */ jsxs(Text, { c: "dimmed", children: [
1829
1806
  "\u2014 ",
1830
1807
  item.label
@@ -1845,7 +1822,7 @@ function ScheduleDetailModal({ opened, onClose, schedule, resourceStatus }) {
1845
1822
  schedule.description && /* @__PURE__ */ jsx(Text, { c: "dimmed", mt: 2, children: schedule.description })
1846
1823
  ] })
1847
1824
  ] }),
1848
- /* @__PURE__ */ jsx(Badge, { variant: "light", color: getStatusColor(schedule.status), children: schedule.status })
1825
+ /* @__PURE__ */ jsx(Badge, { variant: "light", color: getStatusColor2(schedule.status), children: schedule.status })
1849
1826
  ] }),
1850
1827
  resourceNotFound && /* @__PURE__ */ jsxs(Alert, { icon: /* @__PURE__ */ jsx(IconAlertTriangle, { size: 18 }), color: "red", variant: "light", title: "Resource not found", children: [
1851
1828
  "The target resource ",
@@ -1877,14 +1854,14 @@ function ScheduleDetailModal({ opened, onClose, schedule, resourceStatus }) {
1877
1854
  /* @__PURE__ */ jsx(IconClock, { size: 12, color: "var(--color-text-subtle)" }),
1878
1855
  /* @__PURE__ */ jsx(Text, { c: "dimmed", children: "Next Run" })
1879
1856
  ] }) }),
1880
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { children: formatDate2(schedule.nextRunAt) }) })
1857
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { children: formatDate(schedule.nextRunAt) }) })
1881
1858
  ] }),
1882
1859
  /* @__PURE__ */ jsxs(Table.Tr, { children: [
1883
1860
  /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsxs(Group, { gap: 4, children: [
1884
1861
  /* @__PURE__ */ jsx(IconCalendar, { size: 12, color: "var(--color-text-subtle)" }),
1885
1862
  /* @__PURE__ */ jsx(Text, { c: "dimmed", children: "Last Run" })
1886
1863
  ] }) }),
1887
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { children: formatDate2(schedule.lastRunAt) }) })
1864
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { children: formatDate(schedule.lastRunAt) }) })
1888
1865
  ] }),
1889
1866
  /* @__PURE__ */ jsxs(Table.Tr, { children: [
1890
1867
  /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsxs(Group, { gap: 4, children: [
@@ -1911,11 +1888,11 @@ function ScheduleDetailModal({ opened, onClose, schedule, resourceStatus }) {
1911
1888
  /* @__PURE__ */ jsxs(Group, { gap: "lg", children: [
1912
1889
  /* @__PURE__ */ jsxs(Text, { size: "xs", c: "dimmed", children: [
1913
1890
  "Created ",
1914
- formatDate2(schedule.createdAt)
1891
+ formatDate(schedule.createdAt)
1915
1892
  ] }),
1916
1893
  /* @__PURE__ */ jsxs(Text, { size: "xs", c: "dimmed", children: [
1917
1894
  "Updated ",
1918
- formatDate2(schedule.updatedAt)
1895
+ formatDate(schedule.updatedAt)
1919
1896
  ] })
1920
1897
  ] }),
1921
1898
  /* @__PURE__ */ jsx(
@@ -1957,7 +1934,7 @@ function getScheduleTypeLabel(config) {
1957
1934
  return `${config.items.length} scheduled runs`;
1958
1935
  }
1959
1936
  }
1960
- function getStatusColor2(status) {
1937
+ function getStatusColor3(status) {
1961
1938
  switch (status) {
1962
1939
  case "active":
1963
1940
  return "green";
@@ -2055,7 +2032,7 @@ function ScheduleCard({
2055
2032
  /* @__PURE__ */ jsx(IconClock, { size: 12, color: "var(--color-text-subtle)" }),
2056
2033
  /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", style: { whiteSpace: "nowrap" }, children: nextRunLabel })
2057
2034
  ] }),
2058
- /* @__PURE__ */ jsx(Badge, { size: "sm", variant: "light", color: getStatusColor2(schedule.status), children: schedule.status }),
2035
+ /* @__PURE__ */ jsx(Badge, { size: "sm", variant: "light", color: getStatusColor3(schedule.status), children: schedule.status }),
2059
2036
  /* @__PURE__ */ jsxs(Menu, { position: "bottom-end", withinPortal: true, children: [
2060
2037
  /* @__PURE__ */ jsx(Menu.Target, { children: /* @__PURE__ */ jsx(ActionIcon, { variant: "subtle", size: "sm", disabled: isLoading, children: /* @__PURE__ */ jsx(IconDotsVertical, { size: 16 }) }) }),
2061
2038
  /* @__PURE__ */ jsxs(Menu.Dropdown, { children: [
@@ -2740,7 +2717,7 @@ var CommandViewGraphInner = forwardRef(function CommandViewGraphInner2({ graph,
2740
2717
  return /* @__PURE__ */ jsx(
2741
2718
  Box,
2742
2719
  {
2743
- className: Graph_module_css_default.graphContainer,
2720
+ className: `elevasis-graph-root ${Graph_module_css_default.graphContainer}`,
2744
2721
  style: {
2745
2722
  width: "100%",
2746
2723
  height,
@@ -3411,7 +3388,7 @@ function MdxRenderer({ compiledSource }) {
3411
3388
  return /* @__PURE__ */ jsx(Content, { components: mdxComponents });
3412
3389
  }
3413
3390
  var Breadcrumbs = ({ navItems }) => {
3414
- const { Link: Link4 } = useRouterContext();
3391
+ const { Link: Link2 } = useRouterContext();
3415
3392
  const breadcrumbs = useBreadcrumbs({ navItems });
3416
3393
  const items = breadcrumbs.map((item) => {
3417
3394
  const isActive = item.isActive;
@@ -3421,7 +3398,7 @@ var Breadcrumbs = ({ navItems }) => {
3421
3398
  return item.path ? /* @__PURE__ */ jsx(
3422
3399
  Text,
3423
3400
  {
3424
- component: Link4,
3401
+ component: Link2,
3425
3402
  to: item.path,
3426
3403
  size: "sm",
3427
3404
  c: "var(--color-text-subtle)",
@@ -3798,3859 +3775,6 @@ var DEFAULT_KANBAN_CONFIG = {
3798
3775
  closed_lost: { color: "red" },
3799
3776
  nurturing: { color: "grape" }
3800
3777
  };
3801
- var CrmSidebarTop = () => {
3802
- return /* @__PURE__ */ jsx(SubshellSidebarSection, { icon: IconAddressBook, label: "CRM" });
3803
- };
3804
-
3805
- // src/features/crm/workbench/constants.ts
3806
- var SAVED_VIEW_PRESETS = [
3807
- {
3808
- id: "my-deals",
3809
- label: "My Deals",
3810
- iconName: "IconUser",
3811
- target: "/crm/deals"
3812
- },
3813
- {
3814
- id: "overdue",
3815
- label: "Overdue",
3816
- iconName: "IconClockExclamation",
3817
- target: "/crm/deals"
3818
- },
3819
- {
3820
- id: "won-this-month",
3821
- label: "Won this month",
3822
- iconName: "IconTrophy",
3823
- target: "/crm/deals",
3824
- urlFilters: { stage: "closed_won" }
3825
- }
3826
- ];
3827
- var KIND_ICONS = {
3828
- call: IconPhone,
3829
- email: IconMail,
3830
- meeting: IconCalendar,
3831
- other: IconCheckbox
3832
- };
3833
- function formatDueLabel(dueAt) {
3834
- if (!dueAt) return "";
3835
- const date = new Date(dueAt);
3836
- const now = /* @__PURE__ */ new Date();
3837
- const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
3838
- const dueDay = new Date(date.getFullYear(), date.getMonth(), date.getDate());
3839
- const diffDays = Math.round((dueDay.getTime() - today.getTime()) / 864e5);
3840
- if (diffDays < 0) return `${Math.abs(diffDays)}d overdue`;
3841
- if (diffDays === 0) {
3842
- return date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
3843
- }
3844
- return `in ${diffDays}d`;
3845
- }
3846
- function TaskRow({ task, onClick }) {
3847
- const KindIcon2 = KIND_ICONS[task.kind];
3848
- const dueLabel = formatDueLabel(task.dueAt);
3849
- const isOverdue = task.dueAt !== null && new Date(task.dueAt) < /* @__PURE__ */ new Date();
3850
- return /* @__PURE__ */ jsx(
3851
- UnstyledButton,
3852
- {
3853
- onClick,
3854
- style: {
3855
- display: "block",
3856
- width: "100%",
3857
- padding: "4px 6px",
3858
- borderRadius: "var(--mantine-radius-sm)",
3859
- transition: `background-color var(--duration-fast) var(--easing)`
3860
- },
3861
- onMouseEnter: (e) => {
3862
- e.currentTarget.style.backgroundColor = "var(--color-surface-hover)";
3863
- },
3864
- onMouseLeave: (e) => {
3865
- e.currentTarget.style.backgroundColor = "transparent";
3866
- },
3867
- children: /* @__PURE__ */ jsxs(Group, { gap: "xs", wrap: "nowrap", children: [
3868
- /* @__PURE__ */ jsx(KindIcon2, { size: 14, style: { color: "var(--color-text-dimmed)", flexShrink: 0 } }),
3869
- /* @__PURE__ */ jsx(Text, { size: "xs", truncate: true, style: { flex: 1, minWidth: 0 }, children: task.title }),
3870
- dueLabel && /* @__PURE__ */ jsx(Badge, { size: "xs", variant: "light", color: isOverdue ? "red" : "gray", style: { flexShrink: 0 }, children: dueLabel })
3871
- ] })
3872
- }
3873
- );
3874
- }
3875
- function MyTasksPanel({
3876
- onTaskClick,
3877
- onSeeAll,
3878
- footer,
3879
- showSectionLabel = true
3880
- }) {
3881
- const { data, isLoading, isError } = useDealTasksDue({ window: "today_and_overdue" });
3882
- const tasks = data ?? [];
3883
- const total = tasks.length;
3884
- const visible = tasks.slice(0, 5);
3885
- return /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
3886
- showSectionLabel && /* @__PURE__ */ jsxs(Group, { justify: "space-between", mb: 4, children: [
3887
- /* @__PURE__ */ jsx(Text, { fz: "xs", tt: "uppercase", c: "dimmed", fw: 600, children: "My Tasks" }),
3888
- total > 0 && /* @__PURE__ */ jsx(Badge, { size: "xs", variant: "light", children: total })
3889
- ] }),
3890
- isLoading && /* @__PURE__ */ jsx(Center, { py: 4, children: /* @__PURE__ */ jsx(Loader, { size: "xs" }) }),
3891
- isError && /* @__PURE__ */ jsx(Text, { size: "xs", c: "red", children: "Failed to load tasks" }),
3892
- !isLoading && !isError && visible.length === 0 && /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "No tasks due - you're caught up." }),
3893
- !isLoading && !isError && visible.map((task) => /* @__PURE__ */ jsx(TaskRow, { task, onClick: () => onTaskClick(task.dealId) }, task.id)),
3894
- onSeeAll && total > 5 && /* @__PURE__ */ jsx(UnstyledButton, { onClick: onSeeAll, mt: 2, style: { display: "inline-block" }, children: /* @__PURE__ */ jsxs(Text, { size: "xs", c: "dimmed", style: { textDecoration: "underline" }, children: [
3895
- "See all (",
3896
- total,
3897
- ")"
3898
- ] }) }),
3899
- footer
3900
- ] }) });
3901
- }
3902
- var ICON_MAP = {
3903
- IconUser,
3904
- IconClockExclamation,
3905
- IconTrophy
3906
- };
3907
- function SavedViewsPanel({ onViewClick, showSectionLabel = true }) {
3908
- return /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
3909
- showSectionLabel && /* @__PURE__ */ jsx(Text, { fz: "xs", tt: "uppercase", c: "dimmed", fw: 600, mb: 4, children: "Saved Views" }),
3910
- SAVED_VIEW_PRESETS.map((preset) => {
3911
- const Icon = ICON_MAP[preset.iconName];
3912
- return /* @__PURE__ */ jsx(
3913
- UnstyledButton,
3914
- {
3915
- onClick: () => onViewClick(preset),
3916
- style: {
3917
- display: "block",
3918
- width: "100%",
3919
- padding: "4px 6px",
3920
- borderRadius: "var(--mantine-radius-sm)",
3921
- transition: `background-color var(--duration-fast) var(--easing)`
3922
- },
3923
- onMouseEnter: (e) => {
3924
- e.currentTarget.style.backgroundColor = "var(--color-surface-hover)";
3925
- },
3926
- onMouseLeave: (e) => {
3927
- e.currentTarget.style.backgroundColor = "transparent";
3928
- },
3929
- children: /* @__PURE__ */ jsxs(Group, { gap: "xs", wrap: "nowrap", children: [
3930
- /* @__PURE__ */ jsx(Icon, { size: 14, style: { color: "var(--color-text-dimmed)", flexShrink: 0 } }),
3931
- /* @__PURE__ */ jsx(Text, { size: "xs", children: preset.label })
3932
- ] })
3933
- },
3934
- preset.id
3935
- );
3936
- })
3937
- ] }) });
3938
- }
3939
- var KIND_OPTIONS = [
3940
- { value: "call", label: "Call" },
3941
- { value: "email", label: "Email" },
3942
- { value: "meeting", label: "Meeting" },
3943
- { value: "other", label: "Other" }
3944
- ];
3945
- function buildDealLabel(deal) {
3946
- const contact = deal.contact;
3947
- if (!contact) return `Deal ${deal.id.slice(0, 8)}`;
3948
- const name = `${contact.first_name ?? ""} ${contact.last_name ?? ""}`.trim();
3949
- const company = contact.company?.name ?? "\u2014";
3950
- return name ? `${name} \u2013 ${company}` : `Deal ${deal.id.slice(0, 8)}`;
3951
- }
3952
- function QuickCreateActions({ showSectionLabel = true }) {
3953
- const [open, setOpen] = useState(false);
3954
- const [dealId, setDealId] = useState(null);
3955
- const [title, setTitle] = useState("");
3956
- const [description, setDescription] = useState("");
3957
- const [kind, setKind] = useState("other");
3958
- const [dueAt, setDueAt] = useState("");
3959
- const { data: deals } = useDeals();
3960
- const createTask = useCreateDealTask();
3961
- const dealOptions = (deals ?? []).map((deal) => ({
3962
- value: deal.id,
3963
- label: buildDealLabel(deal)
3964
- }));
3965
- function resetForm() {
3966
- setDealId(null);
3967
- setTitle("");
3968
- setDescription("");
3969
- setKind("other");
3970
- setDueAt("");
3971
- }
3972
- function handleClose() {
3973
- setOpen(false);
3974
- resetForm();
3975
- }
3976
- async function handleSubmit() {
3977
- if (!dealId || !title) return;
3978
- await createTask.mutateAsync({
3979
- dealId,
3980
- title,
3981
- description: description || null,
3982
- kind,
3983
- dueAt: dueAt ? new Date(dueAt).toISOString() : null
3984
- });
3985
- handleClose();
3986
- }
3987
- const isSubmitDisabled = !dealId || !title || createTask.isPending;
3988
- return /* @__PURE__ */ jsxs(Fragment, { children: [
3989
- /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
3990
- showSectionLabel && /* @__PURE__ */ jsx(Text, { fz: "xs", tt: "uppercase", c: "dimmed", fw: 600, children: "TASKS" }),
3991
- /* @__PURE__ */ jsx(Button, { variant: "light", size: "xs", leftSection: /* @__PURE__ */ jsx(IconPlus, { size: 14 }), fullWidth: true, onClick: () => setOpen(true), children: "New Task" })
3992
- ] }),
3993
- /* @__PURE__ */ jsxs(Modal, { opened: open, onClose: handleClose, title: "New Task", size: "md", children: [
3994
- /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
3995
- /* @__PURE__ */ jsx(
3996
- Select,
3997
- {
3998
- label: "Deal",
3999
- placeholder: "Select a deal",
4000
- data: dealOptions,
4001
- value: dealId,
4002
- onChange: setDealId,
4003
- searchable: true,
4004
- required: true
4005
- }
4006
- ),
4007
- /* @__PURE__ */ jsx(
4008
- TextInput,
4009
- {
4010
- label: "Title",
4011
- placeholder: "Task title",
4012
- value: title,
4013
- onChange: (e) => setTitle(e.currentTarget.value),
4014
- required: true
4015
- }
4016
- ),
4017
- /* @__PURE__ */ jsx(
4018
- Textarea,
4019
- {
4020
- label: "Description",
4021
- placeholder: "Optional description",
4022
- value: description,
4023
- onChange: (e) => setDescription(e.currentTarget.value),
4024
- autosize: true,
4025
- minRows: 2,
4026
- maxRows: 5
4027
- }
4028
- ),
4029
- /* @__PURE__ */ jsx(
4030
- Select,
4031
- {
4032
- label: "Kind",
4033
- data: KIND_OPTIONS,
4034
- value: kind,
4035
- onChange: (v) => setKind(v ?? "other")
4036
- }
4037
- ),
4038
- /* @__PURE__ */ jsx(
4039
- TextInput,
4040
- {
4041
- type: "datetime-local",
4042
- label: "Due At",
4043
- value: dueAt,
4044
- onChange: (e) => setDueAt(e.currentTarget.value)
4045
- }
4046
- )
4047
- ] }),
4048
- /* @__PURE__ */ jsxs(Group, { justify: "flex-end", mt: "md", children: [
4049
- /* @__PURE__ */ jsx(Button, { variant: "default", onClick: handleClose, children: "Cancel" }),
4050
- /* @__PURE__ */ jsx(Button, { onClick: handleSubmit, loading: createTask.isPending, disabled: isSubmitDisabled, children: "Create" })
4051
- ] })
4052
- ] })
4053
- ] });
4054
- }
4055
- var CRM_ITEMS = [
4056
- { label: "Overview", to: "/crm", icon: IconLayoutGrid, exact: true },
4057
- { label: "Pipeline", to: "/crm/pipeline", icon: IconColumns, exact: false },
4058
- { label: "Deals", to: "/crm/deals", icon: IconFileInvoice, exact: false }
4059
- ];
4060
- var CrmSidebarMiddle = () => {
4061
- const { currentPath, navigate } = useRouterContext();
4062
- return /* @__PURE__ */ jsxs(Stack, { gap: 0, style: { flex: 1, overflowY: "auto" }, children: [
4063
- /* @__PURE__ */ jsx(Stack, { gap: 0, p: "sm", children: CRM_ITEMS.map((item) => {
4064
- const isActive = item.exact ? currentPath === item.to || currentPath === `${item.to}/` : currentPath.startsWith(item.to);
4065
- return /* @__PURE__ */ jsx(
4066
- SubshellNavItem,
4067
- {
4068
- icon: item.icon,
4069
- label: item.label,
4070
- isActive,
4071
- onClick: () => navigate(item.to)
4072
- },
4073
- item.to
4074
- );
4075
- }) }),
4076
- /* @__PURE__ */ jsx(SubshellSidebarSection, { icon: IconChecklist, label: "My Tasks", withTopBorder: true }),
4077
- /* @__PURE__ */ jsx(Stack, { gap: 0, p: "sm", children: /* @__PURE__ */ jsx(
4078
- MyTasksPanel,
4079
- {
4080
- onTaskClick: (dealId) => navigate(`/crm/deals/${dealId}`),
4081
- onSeeAll: () => navigate("/crm/deals"),
4082
- showSectionLabel: false,
4083
- footer: /* @__PURE__ */ jsx(QuickCreateActions, { showSectionLabel: false })
4084
- }
4085
- ) })
4086
- ] });
4087
- };
4088
- var CrmSidebar = () => {
4089
- return /* @__PURE__ */ jsxs(Stack, { gap: 0, style: { height: "100%", display: "flex", flexDirection: "column" }, children: [
4090
- /* @__PURE__ */ jsx(CrmSidebarTop, {}),
4091
- /* @__PURE__ */ jsx(CrmSidebarMiddle, {})
4092
- ] });
4093
- };
4094
- var PIPELINE_FUNNEL_ORDER = [
4095
- "interested",
4096
- "proposal",
4097
- "closing",
4098
- "closed_won",
4099
- "closed_lost",
4100
- "nurturing"
4101
- ];
4102
- var defaultValueOf = (deal) => deal.initial_fee ?? 0;
4103
- function useCrmPipelineSummary(opts) {
4104
- const getValue = opts?.getDealValue ?? defaultValueOf;
4105
- const { data: deals, isLoading, error } = useDeals();
4106
- const data = useMemo(() => {
4107
- const dealList = deals ?? [];
4108
- const stageMap = /* @__PURE__ */ new Map();
4109
- for (const stage of PIPELINE_FUNNEL_ORDER) {
4110
- stageMap.set(stage, { count: 0, totalValue: 0 });
4111
- }
4112
- for (const deal of dealList) {
4113
- const stage = deal.cached_stage;
4114
- if (!stage || !stageMap.has(stage)) continue;
4115
- const entry = stageMap.get(stage);
4116
- entry.count += 1;
4117
- entry.totalValue += getValue(deal);
4118
- }
4119
- return PIPELINE_FUNNEL_ORDER.map((stage) => {
4120
- const entry = stageMap.get(stage);
4121
- return { stage, count: entry.count, totalValue: entry.totalValue };
4122
- });
4123
- }, [deals, getValue]);
4124
- return { data, isLoading, error };
4125
- }
4126
- var CLOSED_STAGES = ["closed_won", "closed_lost"];
4127
- var OPEN_EXCLUDED = CLOSED_STAGES;
4128
- var ZERO_METRICS = {
4129
- totalDeals: 0,
4130
- openDeals: 0,
4131
- wonDeals: 0,
4132
- winRate: 0,
4133
- avgDealSize: 0,
4134
- totalPipelineValue: 0
4135
- };
4136
- function useCrmQuickMetrics() {
4137
- const { data: deals, isLoading, error } = useDeals();
4138
- const data = useMemo(() => {
4139
- const dealList = deals ?? [];
4140
- if (dealList.length === 0) return ZERO_METRICS;
4141
- let openDeals = 0;
4142
- let wonDeals = 0;
4143
- let lostDeals = 0;
4144
- let wonFeeSum = 0;
4145
- let wonFeeCount = 0;
4146
- let pipelineValue = 0;
4147
- for (const deal of dealList) {
4148
- const stage = deal.cached_stage;
4149
- const isOpen = !stage || !OPEN_EXCLUDED.includes(stage);
4150
- const isWon = stage === "closed_won";
4151
- const isLost = stage === "closed_lost";
4152
- if (isOpen) {
4153
- openDeals += 1;
4154
- pipelineValue += deal.initial_fee ?? 0;
4155
- }
4156
- if (isWon) {
4157
- wonDeals += 1;
4158
- if (deal.initial_fee != null) {
4159
- wonFeeSum += deal.initial_fee;
4160
- wonFeeCount += 1;
4161
- }
4162
- }
4163
- if (isLost) {
4164
- lostDeals += 1;
4165
- }
4166
- }
4167
- const winRateDenominator = wonDeals + lostDeals;
4168
- const winRate = winRateDenominator === 0 ? 0 : wonDeals / winRateDenominator;
4169
- const avgDealSize = wonFeeCount === 0 ? 0 : wonFeeSum / wonFeeCount;
4170
- return {
4171
- totalDeals: dealList.length,
4172
- openDeals,
4173
- wonDeals,
4174
- winRate,
4175
- avgDealSize,
4176
- totalPipelineValue: pipelineValue
4177
- };
4178
- }, [deals]);
4179
- return { data, isLoading, error };
4180
- }
4181
- function useRecentCrmActivity(opts) {
4182
- const { apiRequest, isReady, organizationId } = useElevasisServices();
4183
- const limit = opts?.limit ?? 20;
4184
- const query = useQuery({
4185
- queryKey: ["recent-crm-activity", organizationId, limit],
4186
- queryFn: () => apiRequest(`/crm/recent-activity?limit=${limit}`),
4187
- enabled: isReady
4188
- });
4189
- return {
4190
- data: query.data?.entries ?? [],
4191
- isLoading: query.isLoading,
4192
- error: query.error
4193
- };
4194
- }
4195
- var currencyFormatter = new Intl.NumberFormat("en-US", {
4196
- style: "currency",
4197
- currency: "USD",
4198
- maximumFractionDigits: 0
4199
- });
4200
- var STAGE_LABELS = {
4201
- interested: "Interested",
4202
- proposal: "Proposal",
4203
- closing: "Closing",
4204
- closed_won: "Closed Won",
4205
- closed_lost: "Closed Lost",
4206
- nurturing: "Nurturing"
4207
- };
4208
- function PipelineFunnelWidget({ onStageClick, getDealValue }) {
4209
- const { data, isLoading, error } = useCrmPipelineSummary({ getDealValue });
4210
- if (isLoading) {
4211
- return /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsx(Center, { p: "xl", children: /* @__PURE__ */ jsx(Loader, {}) }) });
4212
- }
4213
- if (error) {
4214
- return /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsx(CenteredErrorState, { error, title: "Failed to load pipeline data" }) });
4215
- }
4216
- const totalDeals = data.reduce((sum, s) => sum + s.count, 0);
4217
- const maxCount = Math.max(...data.map((s) => s.count), 1);
4218
- if (totalDeals === 0) {
4219
- return /* @__PURE__ */ jsxs(Paper, { withBorder: true, p: "md", children: [
4220
- /* @__PURE__ */ jsx(CardHeader, { icon: /* @__PURE__ */ jsx(IconColumns, { size: 16 }), title: "Pipeline" }),
4221
- /* @__PURE__ */ jsx(Center, { h: 200, children: /* @__PURE__ */ jsx(Alert, { icon: /* @__PURE__ */ jsx(IconAlertCircle, {}), color: "gray", variant: "light", children: "No deals in the pipeline yet" }) })
4222
- ] });
4223
- }
4224
- return /* @__PURE__ */ jsxs(Paper, { withBorder: true, p: "md", children: [
4225
- /* @__PURE__ */ jsx(
4226
- CardHeader,
4227
- {
4228
- icon: /* @__PURE__ */ jsx(IconColumns, { size: 16 }),
4229
- title: "Pipeline",
4230
- subtitle: `${totalDeals} deal${totalDeals !== 1 ? "s" : ""} total`
4231
- }
4232
- ),
4233
- /* @__PURE__ */ jsx(Box, { children: PIPELINE_FUNNEL_ORDER.map((stage) => {
4234
- const summary = data.find((s) => s.stage === stage);
4235
- const isEmpty = summary.count === 0;
4236
- const barWidth = isEmpty ? 2 : Math.max(4, summary.count / maxCount * 100);
4237
- return /* @__PURE__ */ jsx(
4238
- Box,
4239
- {
4240
- onClick: () => onStageClick(stage),
4241
- style: {
4242
- cursor: "pointer",
4243
- borderRadius: "var(--mantine-radius-sm)",
4244
- padding: "6px 8px",
4245
- transition: `background-color var(--duration-fast) var(--easing)`,
4246
- marginBottom: 4
4247
- },
4248
- onMouseEnter: (e) => {
4249
- e.currentTarget.style.backgroundColor = "var(--color-surface-hover)";
4250
- },
4251
- onMouseLeave: (e) => {
4252
- e.currentTarget.style.backgroundColor = "transparent";
4253
- },
4254
- children: /* @__PURE__ */ jsxs(Group, { gap: "sm", wrap: "nowrap", children: [
4255
- /* @__PURE__ */ jsx(Text, { size: "sm", c: isEmpty ? "dimmed" : void 0, style: { width: 130, flexShrink: 0 }, children: STAGE_LABELS[stage] }),
4256
- /* @__PURE__ */ jsx(Badge, { size: "sm", variant: "light", color: isEmpty ? "gray" : void 0, style: { flexShrink: 0 }, children: summary.count }),
4257
- /* @__PURE__ */ jsx(Badge, { size: "sm", variant: "outline", color: isEmpty ? "gray" : "teal", style: { flexShrink: 0 }, children: currencyFormatter.format(summary.totalValue) }),
4258
- /* @__PURE__ */ jsx(Box, { style: { flex: 1, minWidth: 0 }, children: /* @__PURE__ */ jsx(
4259
- Box,
4260
- {
4261
- style: {
4262
- height: 8,
4263
- width: `${barWidth}%`,
4264
- borderRadius: 4,
4265
- backgroundColor: isEmpty ? "var(--color-border)" : "color-mix(in srgb, var(--color-primary) 70%, transparent)",
4266
- opacity: isEmpty ? 0.4 : 1,
4267
- transition: `width var(--duration-normal) var(--easing)`
4268
- }
4269
- }
4270
- ) })
4271
- ] })
4272
- },
4273
- stage
4274
- );
4275
- }) })
4276
- ] });
4277
- }
4278
- var MAX_VISIBLE = 5;
4279
- function KindIcon({ kind }) {
4280
- const size = 16;
4281
- switch (kind) {
4282
- case "call":
4283
- return /* @__PURE__ */ jsx(IconPhone, { size });
4284
- case "email":
4285
- return /* @__PURE__ */ jsx(IconMail, { size });
4286
- case "meeting":
4287
- return /* @__PURE__ */ jsx(IconCalendar, { size });
4288
- default:
4289
- return /* @__PURE__ */ jsx(IconCheckbox, { size });
4290
- }
4291
- }
4292
- function formatDueDate(dueAt) {
4293
- if (!dueAt) return "No due date";
4294
- return new Date(dueAt).toLocaleDateString();
4295
- }
4296
- function TasksDueWidget({ onTaskClick, onSeeAll }) {
4297
- const { data: tasks, isLoading, error } = useDealTasksDue({ window: "today_and_overdue" });
4298
- if (isLoading) {
4299
- return /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsx(Center, { p: "xl", children: /* @__PURE__ */ jsx(Loader, {}) }) });
4300
- }
4301
- if (error) {
4302
- return /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsx(CenteredErrorState, { error, title: "Failed to load tasks" }) });
4303
- }
4304
- const totalCount = tasks?.length ?? 0;
4305
- const visibleTasks = (tasks ?? []).slice(0, MAX_VISIBLE);
4306
- const hasMore = totalCount > MAX_VISIBLE;
4307
- const seeAllLink = onSeeAll && hasMore ? /* @__PURE__ */ jsxs(Anchor, { size: "sm", onClick: onSeeAll, style: { cursor: "pointer" }, children: [
4308
- "See all (",
4309
- totalCount,
4310
- ")"
4311
- ] }) : void 0;
4312
- if (totalCount === 0) {
4313
- return /* @__PURE__ */ jsxs(Paper, { withBorder: true, p: "md", children: [
4314
- /* @__PURE__ */ jsx(CardHeader, { icon: /* @__PURE__ */ jsx(IconChecklist, { size: 16 }), title: "Tasks Due" }),
4315
- /* @__PURE__ */ jsx(Center, { h: 120, children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "No tasks due today" }) })
4316
- ] });
4317
- }
4318
- return /* @__PURE__ */ jsxs(Paper, { withBorder: true, p: "md", children: [
4319
- /* @__PURE__ */ jsx(
4320
- CardHeader,
4321
- {
4322
- icon: /* @__PURE__ */ jsx(IconChecklist, { size: 16 }),
4323
- title: "Tasks Due",
4324
- subtitle: `${totalCount} task${totalCount !== 1 ? "s" : ""}`,
4325
- rightSection: seeAllLink
4326
- }
4327
- ),
4328
- /* @__PURE__ */ jsx(Stack, { gap: "xs", children: visibleTasks.map((task) => /* @__PURE__ */ jsx(
4329
- Box,
4330
- {
4331
- onClick: () => onTaskClick(task.dealId),
4332
- style: {
4333
- cursor: "pointer",
4334
- borderRadius: "var(--mantine-radius-sm)",
4335
- padding: "6px 8px",
4336
- transition: `background-color var(--duration-fast) var(--easing)`
4337
- },
4338
- onMouseEnter: (e) => {
4339
- e.currentTarget.style.backgroundColor = "var(--color-surface-hover)";
4340
- },
4341
- onMouseLeave: (e) => {
4342
- e.currentTarget.style.backgroundColor = "transparent";
4343
- },
4344
- children: /* @__PURE__ */ jsxs(Group, { gap: "sm", wrap: "nowrap", children: [
4345
- /* @__PURE__ */ jsx(Text, { c: "dimmed", style: { flexShrink: 0 }, children: /* @__PURE__ */ jsx(KindIcon, { kind: task.kind }) }),
4346
- /* @__PURE__ */ jsx(Text, { size: "sm", style: { flex: 1, minWidth: 0 }, truncate: true, children: task.title }),
4347
- /* @__PURE__ */ jsx(Badge, { size: "xs", variant: "light", color: "gray", style: { flexShrink: 0 }, children: formatDueDate(task.dueAt) })
4348
- ] })
4349
- },
4350
- task.id
4351
- )) })
4352
- ] });
4353
- }
4354
- function ActivityKindIcon({ kind }) {
4355
- const size = 16;
4356
- switch (kind) {
4357
- case "note":
4358
- return /* @__PURE__ */ jsx(IconNote, { size });
4359
- case "stage_change":
4360
- return /* @__PURE__ */ jsx(IconArrowRight, { size });
4361
- case "deal_created":
4362
- return /* @__PURE__ */ jsx(IconPlus, { size });
4363
- }
4364
- }
4365
- function formatRelativeTime(occurredAt) {
4366
- const date = new Date(occurredAt);
4367
- const now = /* @__PURE__ */ new Date();
4368
- const diffMs = now.getTime() - date.getTime();
4369
- const diffMin = Math.floor(diffMs / 6e4);
4370
- const diffHr = Math.floor(diffMin / 60);
4371
- const diffDay = Math.floor(diffHr / 24);
4372
- if (diffMin < 1) return "just now";
4373
- if (diffMin < 60) return `${diffMin}m ago`;
4374
- if (diffHr < 24) return `${diffHr}h ago`;
4375
- if (diffDay < 7) return `${diffDay}d ago`;
4376
- return date.toLocaleDateString();
4377
- }
4378
- function ActivityFeedWidget({ onDealClick, limit }) {
4379
- const { data, isLoading, error } = useRecentCrmActivity({ limit: limit ?? 15 });
4380
- if (isLoading) {
4381
- return /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsx(Center, { p: "xl", children: /* @__PURE__ */ jsx(Loader, {}) }) });
4382
- }
4383
- if (error) {
4384
- return /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsx(CenteredErrorState, { error, title: "Failed to load activity" }) });
4385
- }
4386
- if (!data.length) {
4387
- return /* @__PURE__ */ jsxs(Paper, { withBorder: true, p: "md", children: [
4388
- /* @__PURE__ */ jsx(CardHeader, { icon: /* @__PURE__ */ jsx(IconHistory, { size: 16 }), title: "Recent Activity" }),
4389
- /* @__PURE__ */ jsx(Center, { h: 120, children: /* @__PURE__ */ jsx(Alert, { icon: /* @__PURE__ */ jsx(IconAlertCircle, {}), color: "gray", variant: "light", children: "No recent activity" }) })
4390
- ] });
4391
- }
4392
- return /* @__PURE__ */ jsxs(Paper, { withBorder: true, p: "md", children: [
4393
- /* @__PURE__ */ jsx(CardHeader, { icon: /* @__PURE__ */ jsx(IconHistory, { size: 16 }), title: "Recent Activity" }),
4394
- /* @__PURE__ */ jsx(Stack, { gap: 4, children: data.map((entry) => {
4395
- const name = entry.contactName ?? entry.companyName ?? "Unknown";
4396
- return /* @__PURE__ */ jsx(
4397
- Box,
4398
- {
4399
- onClick: () => onDealClick(entry.dealId),
4400
- style: {
4401
- cursor: "pointer",
4402
- borderRadius: "var(--mantine-radius-sm)",
4403
- padding: "6px 8px",
4404
- transition: `background-color var(--duration-fast) var(--easing)`
4405
- },
4406
- onMouseEnter: (e) => {
4407
- e.currentTarget.style.backgroundColor = "var(--color-surface-hover)";
4408
- },
4409
- onMouseLeave: (e) => {
4410
- e.currentTarget.style.backgroundColor = "transparent";
4411
- },
4412
- children: /* @__PURE__ */ jsxs(Group, { gap: "sm", wrap: "nowrap", align: "flex-start", children: [
4413
- /* @__PURE__ */ jsx(Text, { c: "dimmed", style: { flexShrink: 0, paddingTop: 2 }, children: /* @__PURE__ */ jsx(ActivityKindIcon, { kind: entry.kind }) }),
4414
- /* @__PURE__ */ jsx(Box, { style: { flex: 1, minWidth: 0 }, children: /* @__PURE__ */ jsxs(Group, { gap: "xs", wrap: "nowrap", children: [
4415
- /* @__PURE__ */ jsx(Text, { size: "sm", fw: 600, truncate: true, style: { flexShrink: 0, maxWidth: 140 }, children: name }),
4416
- /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", truncate: true, style: { flex: 1, minWidth: 0 }, children: entry.description })
4417
- ] }) }),
4418
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", style: { flexShrink: 0, whiteSpace: "nowrap" }, children: formatRelativeTime(entry.occurredAt) })
4419
- ] })
4420
- },
4421
- entry.id
4422
- );
4423
- }) })
4424
- ] });
4425
- }
4426
- var currencyFormatter2 = new Intl.NumberFormat("en-US", {
4427
- style: "currency",
4428
- currency: "USD",
4429
- maximumFractionDigits: 0
4430
- });
4431
- function formatPercent(value) {
4432
- return `${Math.round(value * 100)}%`;
4433
- }
4434
- function StatTile({ label, value }) {
4435
- return /* @__PURE__ */ jsx(Card, { padding: "sm", children: /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
4436
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: label }),
4437
- /* @__PURE__ */ jsx(Text, { fw: 700, size: "xl", children: value })
4438
- ] }) });
4439
- }
4440
- function MetricsStrip() {
4441
- const { data } = useCrmQuickMetrics();
4442
- return /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsxs(SimpleGrid, { cols: { base: 2, sm: 4 }, children: [
4443
- /* @__PURE__ */ jsx(StatTile, { label: "Total Pipeline Value", value: currencyFormatter2.format(data.totalPipelineValue) }),
4444
- /* @__PURE__ */ jsx(StatTile, { label: "Win Rate", value: formatPercent(data.winRate) }),
4445
- /* @__PURE__ */ jsx(StatTile, { label: "Open Deals", value: String(data.openDeals) }),
4446
- /* @__PURE__ */ jsx(StatTile, { label: "Won This Period", value: String(data.wonDeals) })
4447
- ] }) });
4448
- }
4449
- function CrmOverview({
4450
- onStageClick,
4451
- onDealClick,
4452
- onGoToPipeline,
4453
- getDealValue,
4454
- renderActions
4455
- }) {
4456
- const rightSection = renderActions ? renderActions() : /* @__PURE__ */ jsx(Button, { leftSection: /* @__PURE__ */ jsx(IconColumns, { size: 16 }), variant: "light", size: "sm", onClick: onGoToPipeline, children: "Go to Pipeline" });
4457
- return /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
4458
- /* @__PURE__ */ jsx(
4459
- PageTitleCaption,
4460
- {
4461
- title: "CRM Overview",
4462
- caption: "Pipeline health, tasks, and recent activity at a glance.",
4463
- rightSection
4464
- }
4465
- ),
4466
- /* @__PURE__ */ jsx(MetricsStrip, {}),
4467
- /* @__PURE__ */ jsxs(SimpleGrid, { cols: { base: 1, lg: 2 }, spacing: "md", children: [
4468
- /* @__PURE__ */ jsx(PipelineFunnelWidget, { onStageClick, getDealValue }),
4469
- /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
4470
- /* @__PURE__ */ jsx(TasksDueWidget, { onTaskClick: onDealClick }),
4471
- /* @__PURE__ */ jsx(ActivityFeedWidget, { onDealClick })
4472
- ] })
4473
- ] })
4474
- ] });
4475
- }
4476
- var crmManifest = {
4477
- key: "crm",
4478
- label: "CRM",
4479
- sidebar: CrmSidebar,
4480
- subshellRoutes: ["/crm"],
4481
- navEntry: {
4482
- label: "CRM",
4483
- icon: IconAddressBook,
4484
- link: "/crm",
4485
- featureKey: "acquisition"
4486
- }
4487
- };
4488
-
4489
- // src/features/crm/pages/shared.ts
4490
- var DEAL_STAGE_COLORS = {
4491
- interested: "blue",
4492
- proposal: "yellow",
4493
- closing: "orange",
4494
- closed_won: "green",
4495
- closed_lost: "red",
4496
- nurturing: "grape"
4497
- };
4498
- var DEAL_STAGE_OPTIONS = [
4499
- { value: "interested", label: "Interested" },
4500
- { value: "proposal", label: "Proposal" },
4501
- { value: "closing", label: "Closing" },
4502
- { value: "closed_won", label: "Closed Won" },
4503
- { value: "closed_lost", label: "Closed Lost" },
4504
- { value: "nurturing", label: "Nurturing" }
4505
- ];
4506
- function formatDealStageLabel(stage) {
4507
- if (!stage) return "Unknown";
4508
- return stage.split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
4509
- }
4510
- var sortAccessors = {
4511
- company: (deal) => deal.contact?.company?.name || deal.discovery_data?.company || deal.contact_email?.split("@")[1] || "",
4512
- contact: (deal) => [deal.contact?.first_name, deal.contact?.last_name].filter(Boolean).join(" ") || "",
4513
- email: (deal) => deal.contact_email || "",
4514
- stage: (deal) => deal.cached_stage || "",
4515
- updated: (deal) => deal.updated_at || ""
4516
- };
4517
- function DealsListPage() {
4518
- const navigate = useNavigate();
4519
- const queryClient = useQueryClient();
4520
- const deleteDeal = useDeleteDeal();
4521
- const [stageFilter, setStageFilter] = useState(null);
4522
- const [searchQuery, setSearchQuery] = useState("");
4523
- const [showBatchDelete, setShowBatchDelete] = useState(false);
4524
- const { data: deals, isLoading, error } = useDeals({
4525
- stage: stageFilter || void 0,
4526
- search: searchQuery || void 0
4527
- });
4528
- const { sort, toggleSort } = useTableSort("updated");
4529
- const sortedDeals = useMemo(() => sortData(deals ?? [], sort, sortAccessors), [deals, sort]);
4530
- const pagination = usePaginationState(PAGE_SIZE_DEFAULT, [stageFilter, searchQuery], sortedDeals.length);
4531
- const paginatedDeals = useMemo(
4532
- () => sortedDeals.slice(pagination.offset, pagination.offset + PAGE_SIZE_DEFAULT),
4533
- [sortedDeals, pagination.offset]
4534
- );
4535
- const selection = useTableSelection(paginatedDeals, sortedDeals);
4536
- const handleDeleteSelected = async () => {
4537
- await Promise.all([...selection.selectedIds].map((dealId) => deleteDeal.mutateAsync(dealId)));
4538
- setShowBatchDelete(false);
4539
- selection.clear();
4540
- await queryClient.invalidateQueries({ queryKey: ["deals"] });
4541
- };
4542
- return /* @__PURE__ */ jsxs(SubshellContentContainer, { children: [
4543
- /* @__PURE__ */ jsxs(PageContainer, { children: [
4544
- /* @__PURE__ */ jsx(Stack, { children: /* @__PURE__ */ jsx(PageTitleCaption, { title: "Deals", caption: "Deal pipeline and stage tracking" }) }),
4545
- /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { children: [
4546
- /* @__PURE__ */ jsxs(
4547
- FilterBar,
4548
- {
4549
- actions: /* @__PURE__ */ jsx(
4550
- TableSelectionToolbar,
4551
- {
4552
- selectedCount: selection.selectedCount,
4553
- onDelete: () => setShowBatchDelete(true),
4554
- isDeleting: deleteDeal.isPending
4555
- }
4556
- ),
4557
- children: [
4558
- /* @__PURE__ */ jsx(
4559
- Select,
4560
- {
4561
- placeholder: "All Stages",
4562
- data: DEAL_STAGE_OPTIONS,
4563
- style: { minWidth: 180 },
4564
- size: "sm",
4565
- clearable: true,
4566
- value: stageFilter,
4567
- onChange: (value) => setStageFilter(value ?? null)
4568
- }
4569
- ),
4570
- /* @__PURE__ */ jsx(
4571
- TextInput,
4572
- {
4573
- placeholder: "Search by email...",
4574
- leftSection: /* @__PURE__ */ jsx(IconSearch, { size: 16 }),
4575
- style: { minWidth: 250 },
4576
- size: "sm",
4577
- value: searchQuery,
4578
- onChange: (e) => setSearchQuery(e.currentTarget.value)
4579
- }
4580
- )
4581
- ]
4582
- }
4583
- ),
4584
- isLoading ? /* @__PURE__ */ jsx(Center, { p: "xl", children: /* @__PURE__ */ jsx(Loader, {}) }) : error ? /* @__PURE__ */ jsx(CenteredErrorState, { error, title: "Failed to load deals" }) : !sortedDeals.length ? /* @__PURE__ */ jsx(EmptyState, { icon: IconTargetArrow, title: "No deals found" }) : /* @__PURE__ */ jsxs(Table, { children: [
4585
- /* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
4586
- /* @__PURE__ */ jsx(Table.Th, { w: 40, children: /* @__PURE__ */ jsx(
4587
- Checkbox,
4588
- {
4589
- checked: selection.isPageAllSelected,
4590
- indeterminate: selection.isPagePartiallySelected,
4591
- onChange: selection.togglePage
4592
- }
4593
- ) }),
4594
- /* @__PURE__ */ jsx(SortableHeader, { column: "company", sort, onToggle: toggleSort, children: "Company" }),
4595
- /* @__PURE__ */ jsx(SortableHeader, { column: "contact", sort, onToggle: toggleSort, children: "Contact" }),
4596
- /* @__PURE__ */ jsx(SortableHeader, { column: "email", sort, onToggle: toggleSort, children: "Email" }),
4597
- /* @__PURE__ */ jsx(SortableHeader, { column: "stage", sort, onToggle: toggleSort, children: "Stage" }),
4598
- /* @__PURE__ */ jsx(SortableHeader, { column: "updated", sort, onToggle: toggleSort, children: "Updated" })
4599
- ] }) }),
4600
- /* @__PURE__ */ jsx(Table.Tbody, { children: paginatedDeals.map((deal) => {
4601
- const discoveryData = deal.discovery_data;
4602
- const companyName = deal.contact?.company?.name || discoveryData?.company || deal.contact_email?.split("@")[1] || "-";
4603
- const contactName = [deal.contact?.first_name, deal.contact?.last_name].filter(Boolean).join(" ");
4604
- return /* @__PURE__ */ jsxs(
4605
- Table.Tr,
4606
- {
4607
- style: { cursor: "pointer" },
4608
- onClick: () => navigate({
4609
- to: "/crm/deals/$dealId",
4610
- params: { dealId: deal.id }
4611
- }),
4612
- children: [
4613
- /* @__PURE__ */ jsx(Table.Td, { onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx(
4614
- Checkbox,
4615
- {
4616
- checked: selection.isSelected(deal.id),
4617
- onChange: () => selection.toggle(deal.id)
4618
- }
4619
- ) }),
4620
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { fw: 500, children: companyName }) }),
4621
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", children: contactName || "-" }) }),
4622
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: deal.contact_email || "-" }) }),
4623
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Badge, { variant: "light", color: DEAL_STAGE_COLORS[deal.cached_stage || ""] || "gray", size: "sm", children: formatDealStageLabel(deal.cached_stage) }) }),
4624
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: formatTimeAgo(deal.updated_at) }) })
4625
- ]
4626
- },
4627
- deal.id
4628
- );
4629
- }) })
4630
- ] }),
4631
- sortedDeals.length > PAGE_SIZE_DEFAULT && /* @__PURE__ */ jsx(Group, { justify: "center", children: /* @__PURE__ */ jsx(
4632
- Pagination,
4633
- {
4634
- value: pagination.page,
4635
- onChange: pagination.setPage,
4636
- total: pagination.totalPages(sortedDeals.length),
4637
- size: "sm"
4638
- }
4639
- ) })
4640
- ] }) })
4641
- ] }),
4642
- /* @__PURE__ */ jsx(
4643
- CustomModal,
4644
- {
4645
- opened: showBatchDelete,
4646
- onClose: () => !deleteDeal.isPending && setShowBatchDelete(false),
4647
- size: "sm",
4648
- loading: deleteDeal.isPending,
4649
- children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
4650
- /* @__PURE__ */ jsxs(Group, { gap: "sm", children: [
4651
- /* @__PURE__ */ jsx(IconAlertTriangle, { size: 24, color: "var(--color-error)" }),
4652
- /* @__PURE__ */ jsxs(Title, { order: 4, children: [
4653
- "Delete ",
4654
- selection.selectedCount,
4655
- " Deals"
4656
- ] })
4657
- ] }),
4658
- /* @__PURE__ */ jsxs(Text, { size: "sm", children: [
4659
- "Are you sure you want to delete",
4660
- " ",
4661
- /* @__PURE__ */ jsx(Text, { span: true, fw: 600, children: selection.selectedCount }),
4662
- " ",
4663
- "selected deals?"
4664
- ] }),
4665
- /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "This action cannot be undone." }),
4666
- /* @__PURE__ */ jsxs(Group, { justify: "flex-end", mt: "md", children: [
4667
- /* @__PURE__ */ jsx(Button, { variant: "light", onClick: () => setShowBatchDelete(false), disabled: deleteDeal.isPending, children: "Cancel" }),
4668
- /* @__PURE__ */ jsx(Button, { color: "red", loading: deleteDeal.isPending, onClick: () => void handleDeleteSelected(), children: "Delete" })
4669
- ] })
4670
- ] })
4671
- }
4672
- )
4673
- ] });
4674
- }
4675
- function DealDetailPage({ dealId, renderActions, onDealLoaded }) {
4676
- const navigate = useNavigate();
4677
- const deleteDeal = useDeleteDeal();
4678
- const { data: deal, isLoading, error } = useDealDetail(dealId);
4679
- const [deleteModalOpen, setDeleteModalOpen] = useState(false);
4680
- useEffect(() => {
4681
- if (deal) onDealLoaded?.(deal);
4682
- }, [deal, onDealLoaded]);
4683
- const title = deal ? `${[deal.contact?.first_name, deal.contact?.last_name].filter(Boolean).join(" ") || "Unknown"} Deal` : "Deal Detail";
4684
- const contactName = useMemo(
4685
- () => deal ? [deal.contact?.first_name, deal.contact?.last_name].filter(Boolean).join(" ") : "",
4686
- [deal]
4687
- );
4688
- const companyName = useMemo(() => {
4689
- if (!deal) return null;
4690
- return deal.contact?.company?.name || deal.discovery_data?.company || deal.contact_email?.split("@")[1] || "Unknown";
4691
- }, [deal]);
4692
- const headerActions = deal ? /* @__PURE__ */ jsxs(Group, { children: [
4693
- /* @__PURE__ */ jsx(
4694
- Button,
4695
- {
4696
- variant: "light",
4697
- size: "sm",
4698
- leftSection: /* @__PURE__ */ jsx(IconArrowLeft, { size: 16 }),
4699
- onClick: () => navigate({ to: "/crm/deals" }),
4700
- children: "Deals"
4701
- }
4702
- ),
4703
- deal.proposal_pdf_url && /* @__PURE__ */ jsx(
4704
- Button,
4705
- {
4706
- variant: "light",
4707
- leftSection: /* @__PURE__ */ jsx(IconFileText, { size: 16 }),
4708
- onClick: () => window.open(deal.proposal_pdf_url, "_blank"),
4709
- children: "View Proposal"
4710
- }
4711
- ),
4712
- renderActions?.(deal),
4713
- /* @__PURE__ */ jsx(ActionIcon, { variant: "subtle", color: "red", onClick: () => setDeleteModalOpen(true), children: /* @__PURE__ */ jsx(IconTrash, { size: 16 }) })
4714
- ] }) : /* @__PURE__ */ jsx(Button, { variant: "light", size: "sm", leftSection: /* @__PURE__ */ jsx(IconArrowLeft, { size: 16 }), onClick: () => navigate({ to: "/crm/deals" }), children: "Deals" });
4715
- if (isLoading) {
4716
- return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsxs(PageContainer, { children: [
4717
- /* @__PURE__ */ jsx(Stack, { children: /* @__PURE__ */ jsx(PageTitleCaption, { title, caption: "Loading deal details...", rightSection: headerActions }) }),
4718
- /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsx(Center, { p: "xl", children: /* @__PURE__ */ jsx(Loader, {}) }) })
4719
- ] }) });
4720
- }
4721
- if (error) {
4722
- return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsxs(PageContainer, { children: [
4723
- /* @__PURE__ */ jsx(Stack, { children: /* @__PURE__ */ jsx(PageTitleCaption, { title, caption: "Unable to load deal details", rightSection: headerActions }) }),
4724
- /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsx(CenteredErrorState, { error, title: "Failed to load deal" }) })
4725
- ] }) });
4726
- }
4727
- if (!deal) {
4728
- return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsxs(PageContainer, { children: [
4729
- /* @__PURE__ */ jsx(Stack, { children: /* @__PURE__ */ jsx(PageTitleCaption, { title, caption: "Deal not found", rightSection: headerActions }) }),
4730
- /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsx(EmptyState, { icon: IconTrash, title: "Deal not found", description: "The selected deal no longer exists." }) })
4731
- ] }) });
4732
- }
4733
- const activityLog = deal.activity_log || [];
4734
- return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsxs(PageContainer, { children: [
4735
- /* @__PURE__ */ jsx(Stack, { children: /* @__PURE__ */ jsx(
4736
- PageTitleCaption,
4737
- {
4738
- title,
4739
- caption: `${companyName || "Unknown"} - ${formatDealStageLabel(deal.cached_stage)}`,
4740
- rightSection: headerActions
4741
- }
4742
- ) }),
4743
- /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsxs(Tabs, { defaultValue: "details", children: [
4744
- /* @__PURE__ */ jsxs(Tabs.List, { children: [
4745
- /* @__PURE__ */ jsx(Tabs.Tab, { value: "details", children: "Details" }),
4746
- /* @__PURE__ */ jsx(Tabs.Tab, { value: "discovery", children: "Discovery Data" }),
4747
- /* @__PURE__ */ jsx(Tabs.Tab, { value: "activity", children: "Activity" })
4748
- ] }),
4749
- /* @__PURE__ */ jsx(Tabs.Panel, { value: "details", pt: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
4750
- /* @__PURE__ */ jsx(Card, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
4751
- /* @__PURE__ */ jsx(Title, { order: 4, children: "Deal" }),
4752
- /* @__PURE__ */ jsxs(Group, { children: [
4753
- /* @__PURE__ */ jsx(Text, { fw: 500, children: "Stage:" }),
4754
- /* @__PURE__ */ jsx(Badge, { color: DEAL_STAGE_COLORS[deal.cached_stage || ""] || "gray", children: formatDealStageLabel(deal.cached_stage) })
4755
- ] }),
4756
- /* @__PURE__ */ jsxs(Group, { children: [
4757
- /* @__PURE__ */ jsx(Text, { fw: 500, children: "Sent:" }),
4758
- /* @__PURE__ */ jsx(Text, { children: deal.proposal_sent_at ? new Date(deal.proposal_sent_at).toLocaleString() : "N/A" })
4759
- ] }),
4760
- /* @__PURE__ */ jsxs(Group, { children: [
4761
- /* @__PURE__ */ jsx(Text, { fw: 500, children: "Envelope ID:" }),
4762
- /* @__PURE__ */ jsx(Text, { children: deal.signature_envelope_id || "N/A" })
4763
- ] })
4764
- ] }) }),
4765
- /* @__PURE__ */ jsx(Card, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
4766
- /* @__PURE__ */ jsx(Title, { order: 4, children: "Contact" }),
4767
- /* @__PURE__ */ jsxs(SimpleGrid, { cols: { base: 1, sm: 2 }, spacing: "sm", children: [
4768
- /* @__PURE__ */ jsxs(Group, { children: [
4769
- /* @__PURE__ */ jsx(Text, { fw: 500, children: "Name:" }),
4770
- /* @__PURE__ */ jsx(Text, { children: contactName || "N/A" })
4771
- ] }),
4772
- /* @__PURE__ */ jsxs(Group, { children: [
4773
- /* @__PURE__ */ jsx(Text, { fw: 500, children: "Email:" }),
4774
- /* @__PURE__ */ jsx(Text, { children: deal.contact?.email || deal.contact_email || "N/A" })
4775
- ] }),
4776
- /* @__PURE__ */ jsxs(Group, { children: [
4777
- /* @__PURE__ */ jsx(Text, { fw: 500, children: "Title:" }),
4778
- /* @__PURE__ */ jsx(Text, { children: deal.contact?.title || "N/A" })
4779
- ] }),
4780
- /* @__PURE__ */ jsxs(Group, { children: [
4781
- /* @__PURE__ */ jsx(Text, { fw: 500, children: "Pipeline Status:" }),
4782
- /* @__PURE__ */ jsx(Text, { size: "sm", children: deal.contact?.pipeline_status ? Object.entries(deal.contact.pipeline_status).map(([key, val]) => {
4783
- const status = val?.status;
4784
- return status ? `${key}: ${status}` : null;
4785
- }).filter(Boolean).join(", ") || "N/A" : "N/A" })
4786
- ] }),
4787
- deal.contact?.headline && /* @__PURE__ */ jsxs(Group, { children: [
4788
- /* @__PURE__ */ jsx(Text, { fw: 500, children: "Headline:" }),
4789
- /* @__PURE__ */ jsx(Text, { children: deal.contact.headline })
4790
- ] }),
4791
- deal.contact?.linkedin_url && /* @__PURE__ */ jsxs(Group, { children: [
4792
- /* @__PURE__ */ jsx(Text, { fw: 500, children: "LinkedIn:" }),
4793
- /* @__PURE__ */ jsx(Text, { component: "a", href: deal.contact.linkedin_url, target: "_blank", c: "blue", children: "View Profile" })
4794
- ] })
4795
- ] })
4796
- ] }) }),
4797
- /* @__PURE__ */ jsx(Card, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
4798
- /* @__PURE__ */ jsx(Title, { order: 4, children: "Company" }),
4799
- /* @__PURE__ */ jsxs(SimpleGrid, { cols: { base: 1, sm: 2 }, spacing: "sm", children: [
4800
- /* @__PURE__ */ jsxs(Group, { children: [
4801
- /* @__PURE__ */ jsx(Text, { fw: 500, children: "Name:" }),
4802
- /* @__PURE__ */ jsx(Text, { children: deal.contact?.company?.name || "N/A" })
4803
- ] }),
4804
- /* @__PURE__ */ jsxs(Group, { children: [
4805
- /* @__PURE__ */ jsx(Text, { fw: 500, children: "Domain:" }),
4806
- /* @__PURE__ */ jsx(Text, { children: deal.contact?.company?.domain || "N/A" })
4807
- ] }),
4808
- /* @__PURE__ */ jsxs(Group, { children: [
4809
- /* @__PURE__ */ jsx(Text, { fw: 500, children: "Segment:" }),
4810
- /* @__PURE__ */ jsx(Text, { children: deal.contact?.company?.segment || "N/A" })
4811
- ] }),
4812
- /* @__PURE__ */ jsxs(Group, { children: [
4813
- /* @__PURE__ */ jsx(Text, { fw: 500, children: "Category:" }),
4814
- /* @__PURE__ */ jsx(Text, { children: deal.contact?.company?.category || "N/A" })
4815
- ] }),
4816
- /* @__PURE__ */ jsxs(Group, { children: [
4817
- /* @__PURE__ */ jsx(Text, { fw: 500, children: "Employees:" }),
4818
- /* @__PURE__ */ jsx(Text, { children: deal.contact?.company?.num_employees || "N/A" })
4819
- ] }),
4820
- deal.contact?.company?.website && /* @__PURE__ */ jsxs(Group, { children: [
4821
- /* @__PURE__ */ jsx(Text, { fw: 500, children: "Website:" }),
4822
- /* @__PURE__ */ jsx(Text, { component: "a", href: deal.contact.company.website, target: "_blank", c: "blue", children: deal.contact.company.website })
4823
- ] })
4824
- ] })
4825
- ] }) }),
4826
- ["proposal_signed", "payment_sent", "closed_won"].includes(deal.cached_stage || "") && /* @__PURE__ */ jsx(Card, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
4827
- /* @__PURE__ */ jsx(Title, { order: 4, children: "Payment" }),
4828
- /* @__PURE__ */ jsxs(SimpleGrid, { cols: { base: 1, sm: 2 }, spacing: "sm", children: [
4829
- /* @__PURE__ */ jsxs(Group, { children: [
4830
- /* @__PURE__ */ jsx(Text, { fw: 500, children: "Initial Fee:" }),
4831
- /* @__PURE__ */ jsxs(Text, { children: [
4832
- "$",
4833
- deal.initial_fee?.toLocaleString() || "Not set"
4834
- ] })
4835
- ] }),
4836
- /* @__PURE__ */ jsxs(Group, { children: [
4837
- /* @__PURE__ */ jsx(Text, { fw: 500, children: "Monthly Fee:" }),
4838
- /* @__PURE__ */ jsxs(Text, { children: [
4839
- "$",
4840
- deal.monthly_fee?.toLocaleString() || "Not set",
4841
- "/mo"
4842
- ] })
4843
- ] })
4844
- ] }),
4845
- deal.stripe_payment_link && /* @__PURE__ */ jsxs(Group, { children: [
4846
- /* @__PURE__ */ jsx(Text, { fw: 500, children: "Payment Link:" }),
4847
- /* @__PURE__ */ jsx(Text, { component: "a", href: deal.stripe_payment_link, target: "_blank", c: "blue", children: deal.stripe_payment_link })
4848
- ] })
4849
- ] }) })
4850
- ] }) }),
4851
- /* @__PURE__ */ jsx(Tabs.Panel, { value: "discovery", pt: "md", children: /* @__PURE__ */ jsx(Card, { withBorder: true, children: /* @__PURE__ */ jsx(Code, { block: true, children: JSON.stringify(deal.discovery_data, null, 2) }) }) }),
4852
- /* @__PURE__ */ jsx(Tabs.Panel, { value: "activity", pt: "md", children: /* @__PURE__ */ jsx(ActivityTimeline, { activities: activityLog }) })
4853
- ] }) }),
4854
- /* @__PURE__ */ jsx(CustomModal, { opened: deleteModalOpen, onClose: () => setDeleteModalOpen(false), size: "sm", children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
4855
- /* @__PURE__ */ jsxs(Group, { justify: "space-between", align: "flex-start", children: [
4856
- /* @__PURE__ */ jsx(Title, { order: 4, children: "Delete deal" }),
4857
- /* @__PURE__ */ jsx(ActionIcon, { variant: "subtle", onClick: () => setDeleteModalOpen(false), children: /* @__PURE__ */ jsx(IconX, { size: 18 }) })
4858
- ] }),
4859
- /* @__PURE__ */ jsxs(Text, { size: "sm", children: [
4860
- "Are you sure you want to delete the deal for ",
4861
- /* @__PURE__ */ jsx("strong", { children: contactName || deal.contact_email }),
4862
- "? This will cancel any active schedules and pending tasks for this contact."
4863
- ] }),
4864
- (deal.signature_envelope_id || deal.stripe_payment_link) && /* @__PURE__ */ jsxs(Alert, { color: "yellow", variant: "light", children: [
4865
- "This deal has ",
4866
- deal.signature_envelope_id ? "a signed contract" : "",
4867
- deal.signature_envelope_id && deal.stripe_payment_link ? " and " : "",
4868
- deal.stripe_payment_link ? "an active payment link" : "",
4869
- " that will not be automatically cleaned up."
4870
- ] }),
4871
- /* @__PURE__ */ jsx(Divider, {}),
4872
- /* @__PURE__ */ jsxs(Group, { justify: "flex-end", children: [
4873
- /* @__PURE__ */ jsx(Button, { variant: "default", onClick: () => setDeleteModalOpen(false), disabled: deleteDeal.isPending, children: "Cancel" }),
4874
- /* @__PURE__ */ jsx(
4875
- Button,
4876
- {
4877
- color: "red",
4878
- loading: deleteDeal.isPending,
4879
- onClick: () => deleteDeal.mutate(deal.id, {
4880
- onSuccess: () => {
4881
- setDeleteModalOpen(false);
4882
- void navigate({ to: "/crm/deals" });
4883
- }
4884
- }),
4885
- children: "Delete deal"
4886
- }
4887
- )
4888
- ] })
4889
- ] }) })
4890
- ] }) });
4891
- }
4892
- var LeadGenSidebarTop = () => {
4893
- return /* @__PURE__ */ jsx(SubshellSidebarSection, { icon: IconTarget, label: "Lead Gen" });
4894
- };
4895
- var LEAD_GEN_ITEMS = [
4896
- { label: "Overview", to: "/lead-gen", icon: IconLayoutGrid, exact: true },
4897
- { label: "Lists", to: "/lead-gen/lists", icon: IconList, exact: false },
4898
- { label: "Companies", to: "/lead-gen/companies", icon: IconBuilding, exact: false },
4899
- { label: "Contacts", to: "/lead-gen/contacts", icon: IconAddressBook, exact: false },
4900
- { label: "Deliverability", to: "/lead-gen/deliverability", icon: IconMailCheck, exact: false }
4901
- ];
4902
- var LeadGenSidebarMiddle = () => {
4903
- const { currentPath, navigate } = useRouterContext();
4904
- 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) => {
4905
- const isActive = item.exact ? currentPath === item.to || currentPath === `${item.to}/` : currentPath.startsWith(item.to);
4906
- return /* @__PURE__ */ jsx(
4907
- SubshellNavItem,
4908
- {
4909
- icon: item.icon,
4910
- label: item.label,
4911
- isActive,
4912
- onClick: () => navigate(item.to)
4913
- },
4914
- item.to
4915
- );
4916
- }) }) });
4917
- };
4918
- var LeadGenSidebar = () => {
4919
- return /* @__PURE__ */ jsxs(Stack, { gap: 0, style: { height: "100%", display: "flex", flexDirection: "column" }, children: [
4920
- /* @__PURE__ */ jsx(LeadGenSidebarTop, {}),
4921
- /* @__PURE__ */ jsx(LeadGenSidebarMiddle, {})
4922
- ] });
4923
- };
4924
- var leadGenManifest = {
4925
- key: "lead-gen",
4926
- label: "Lead Gen",
4927
- sidebar: LeadGenSidebar,
4928
- subshellRoutes: ["/lead-gen"],
4929
- navEntry: {
4930
- label: "Lead Gen",
4931
- icon: IconTarget,
4932
- link: "/lead-gen",
4933
- featureKey: "acquisition"
4934
- }
4935
- };
4936
- var LEAD_GEN_ROUTE_LINKS = [
4937
- { label: "Overview", to: "/lead-gen" },
4938
- { label: "Lists", to: "/lead-gen/lists" },
4939
- { label: "Companies", to: "/lead-gen/companies" },
4940
- { label: "Contacts", to: "/lead-gen/contacts" },
4941
- { label: "Deliverability", to: "/lead-gen/deliverability" }
4942
- ];
4943
- function LeadGenRouteShell({
4944
- title,
4945
- caption,
4946
- body,
4947
- links = LEAD_GEN_ROUTE_LINKS
4948
- }) {
4949
- return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsx(PageContainer, { children: /* @__PURE__ */ jsxs(Stack, { children: [
4950
- /* @__PURE__ */ jsx(PageTitleCaption, { title, caption }),
4951
- /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "lg", children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
4952
- body,
4953
- /* @__PURE__ */ jsx(Text, { children: links.map((link, index) => /* @__PURE__ */ jsxs("span", { children: [
4954
- index > 0 ? " | " : null,
4955
- /* @__PURE__ */ jsx(Anchor, { component: Link$1, to: link.to, children: link.label })
4956
- ] }, link.to)) })
4957
- ] }) })
4958
- ] }) }) });
4959
- }
4960
- function formatDate3(dateValue) {
4961
- const date = typeof dateValue === "string" ? new Date(dateValue) : dateValue;
4962
- return date.toLocaleDateString("en-US", {
4963
- month: "short",
4964
- day: "numeric",
4965
- year: "numeric"
4966
- });
4967
- }
4968
- function getStatusColor3(status) {
4969
- return status === "active" ? "green" : status === "invalid" ? "red" : "gray";
4970
- }
4971
- function getEnrichmentColor(status) {
4972
- switch (status) {
4973
- case "complete":
4974
- return "green";
4975
- case "pending":
4976
- return "yellow";
4977
- case "failed":
4978
- return "red";
4979
- default:
4980
- return "gray";
4981
- }
4982
- }
4983
- function getEnrichmentStatus(enrichmentData) {
4984
- if (!enrichmentData || typeof enrichmentData !== "object") return "pending";
4985
- const website = enrichmentData.website;
4986
- const linkedin = enrichmentData.linkedin;
4987
- if (website === "complete" && linkedin === "complete") return "complete";
4988
- if (website === "failed" || linkedin === "failed") return "failed";
4989
- return "pending";
4990
- }
4991
- function formatName(parts, fallback) {
4992
- const name = parts.filter(Boolean).join(" ").trim();
4993
- return name || fallback;
4994
- }
4995
- function CompanyDetailModal({ company, onClose }) {
4996
- return /* @__PURE__ */ jsx(CustomModal, { opened: !!company, onClose, size: "xl", children: company ? /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
4997
- /* @__PURE__ */ jsxs(Group, { justify: "space-between", align: "flex-start", children: [
4998
- /* @__PURE__ */ jsxs("div", { children: [
4999
- /* @__PURE__ */ jsx(Title, { order: 3, children: company.name }),
5000
- company.domain ? /* @__PURE__ */ jsxs(Anchor, { href: `https://${company.domain}`, target: "_blank", size: "sm", c: "dimmed", children: [
5001
- company.domain,
5002
- " ",
5003
- /* @__PURE__ */ jsx(IconExternalLink, { size: 12, style: { verticalAlign: "middle" } })
5004
- ] }) : null
5005
- ] }),
5006
- /* @__PURE__ */ jsx(ActionIcon, { variant: "subtle", onClick: onClose, children: /* @__PURE__ */ jsx(IconX, { size: 18 }) })
5007
- ] }),
5008
- /* @__PURE__ */ jsx(Divider, {}),
5009
- /* @__PURE__ */ jsxs(Box, { children: [
5010
- /* @__PURE__ */ jsx(Title, { order: 4, mb: "xs", children: "Firmographics" }),
5011
- /* @__PURE__ */ jsxs(SimpleGrid, { cols: 3, children: [
5012
- /* @__PURE__ */ jsxs("div", { children: [
5013
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Segment" }),
5014
- /* @__PURE__ */ jsx(Text, { size: "sm", children: company.segment || "-" })
5015
- ] }),
5016
- /* @__PURE__ */ jsxs("div", { children: [
5017
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Category" }),
5018
- /* @__PURE__ */ jsx(Text, { size: "sm", children: company.category || "-" })
5019
- ] }),
5020
- /* @__PURE__ */ jsxs("div", { children: [
5021
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Employees" }),
5022
- /* @__PURE__ */ jsx(Text, { size: "sm", children: company.numEmployees || "-" })
5023
- ] }),
5024
- /* @__PURE__ */ jsxs("div", { children: [
5025
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Founded" }),
5026
- /* @__PURE__ */ jsx(Text, { size: "sm", children: company.foundedYear || "-" })
5027
- ] }),
5028
- /* @__PURE__ */ jsxs("div", { children: [
5029
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Location" }),
5030
- /* @__PURE__ */ jsx(Text, { size: "sm", children: [company.locationCity, company.locationState].filter(Boolean).join(", ") || "-" })
5031
- ] }),
5032
- /* @__PURE__ */ jsxs("div", { children: [
5033
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Contacts" }),
5034
- /* @__PURE__ */ jsx(Text, { size: "sm", children: company.contactCount })
5035
- ] })
5036
- ] })
5037
- ] }),
5038
- company.enrichmentData ? /* @__PURE__ */ jsxs(Box, { children: [
5039
- /* @__PURE__ */ jsx(Title, { order: 4, mb: "xs", children: "Enrichment Data" }),
5040
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", style: { whiteSpace: "pre-wrap" }, children: JSON.stringify(company.enrichmentData, null, 2) })
5041
- ] }) : null,
5042
- /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
5043
- company.linkedinUrl ? /* @__PURE__ */ jsxs(Anchor, { href: company.linkedinUrl, target: "_blank", size: "sm", children: [
5044
- "LinkedIn ",
5045
- /* @__PURE__ */ jsx(IconExternalLink, { size: 12 })
5046
- ] }) : null,
5047
- company.website ? /* @__PURE__ */ jsxs(Anchor, { href: company.website, target: "_blank", size: "sm", children: [
5048
- "Website ",
5049
- /* @__PURE__ */ jsx(IconExternalLink, { size: 12 })
5050
- ] }) : null
5051
- ] }),
5052
- /* @__PURE__ */ jsxs(Text, { size: "xs", c: "dimmed", children: [
5053
- "Created: ",
5054
- formatDate3(company.createdAt),
5055
- company.updatedAt && ` | Updated: ${formatDate3(company.updatedAt)}`
5056
- ] })
5057
- ] }) : null });
5058
- }
5059
- function ContactDetailModal({ contact, onClose }) {
5060
- return /* @__PURE__ */ jsx(CustomModal, { opened: !!contact, onClose, size: "xl", children: contact ? /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
5061
- /* @__PURE__ */ jsxs(Group, { justify: "space-between", align: "flex-start", children: [
5062
- /* @__PURE__ */ jsxs("div", { children: [
5063
- /* @__PURE__ */ jsx(Title, { order: 3, children: formatName([contact.firstName, contact.lastName], contact.email) }),
5064
- /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: contact.email }),
5065
- contact.title ? /* @__PURE__ */ jsx(Text, { size: "sm", children: contact.title }) : null
5066
- ] }),
5067
- /* @__PURE__ */ jsx(ActionIcon, { variant: "subtle", onClick: onClose, children: /* @__PURE__ */ jsx(IconX, { size: 18 }) })
5068
- ] }),
5069
- /* @__PURE__ */ jsx(Divider, {}),
5070
- /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
5071
- /* @__PURE__ */ jsx(Badge, { color: getStatusColor3(contact.status), children: contact.status }),
5072
- contact.openingLine ? /* @__PURE__ */ jsx(Badge, { color: "green", children: "Personalized" }) : null
5073
- ] }),
5074
- contact.company ? /* @__PURE__ */ jsxs(Box, { children: [
5075
- /* @__PURE__ */ jsx(Title, { order: 4, mb: "xs", children: "Company" }),
5076
- /* @__PURE__ */ jsxs(Card, { withBorder: true, children: [
5077
- /* @__PURE__ */ jsx(Text, { fw: 500, children: contact.company.name }),
5078
- contact.company.domain ? /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: contact.company.domain }) : null
5079
- ] })
5080
- ] }) : null,
5081
- /* @__PURE__ */ jsxs(Box, { children: [
5082
- /* @__PURE__ */ jsx(Title, { order: 4, mb: "xs", children: "Contact Information" }),
5083
- /* @__PURE__ */ jsxs(SimpleGrid, { cols: 2, children: [
5084
- /* @__PURE__ */ jsxs("div", { children: [
5085
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Headline" }),
5086
- /* @__PURE__ */ jsx(Text, { size: "sm", children: contact.headline || "-" })
5087
- ] }),
5088
- /* @__PURE__ */ jsxs("div", { children: [
5089
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "LinkedIn" }),
5090
- contact.linkedinUrl ? /* @__PURE__ */ jsxs(Anchor, { href: contact.linkedinUrl, target: "_blank", size: "sm", children: [
5091
- "View Profile ",
5092
- /* @__PURE__ */ jsx(IconExternalLink, { size: 12 })
5093
- ] }) : /* @__PURE__ */ jsx(Text, { size: "sm", children: "-" })
5094
- ] })
5095
- ] })
5096
- ] }),
5097
- contact.filterReason ? /* @__PURE__ */ jsxs(Box, { children: [
5098
- /* @__PURE__ */ jsx(Title, { order: 4, mb: "xs", children: "Qualification" }),
5099
- /* @__PURE__ */ jsx(Text, { size: "sm", c: "red", children: contact.filterReason })
5100
- ] }) : null,
5101
- contact.openingLine ? /* @__PURE__ */ jsxs(Box, { children: [
5102
- /* @__PURE__ */ jsx(Title, { order: 4, mb: "xs", children: "Personalization" }),
5103
- /* @__PURE__ */ jsx(Text, { size: "sm", children: contact.openingLine })
5104
- ] }) : null,
5105
- /* @__PURE__ */ jsxs(Text, { size: "xs", c: "dimmed", children: [
5106
- "Created: ",
5107
- formatDate3(contact.createdAt),
5108
- contact.updatedAt && ` | Updated: ${formatDate3(contact.updatedAt)}`
5109
- ] })
5110
- ] }) : null });
5111
- }
5112
- var LIST_TEMPLATE_OPTIONS = [
5113
- {
5114
- value: "blank",
5115
- label: "Blank",
5116
- description: "Create an empty draft list with qualification defaults and no pipeline steps."
5117
- },
5118
- {
5119
- value: "full_pipeline",
5120
- label: "Full 6-Stage",
5121
- description: "Create a complete lead-gen pipeline from scrape through personalization."
5122
- },
5123
- {
5124
- value: "personalize_only",
5125
- label: "Personalize Only",
5126
- description: "Start with personalization and upload-focused steps for already-prepared contacts."
5127
- },
5128
- {
5129
- value: "email_refresh",
5130
- label: "Email Refresh",
5131
- description: "Run discovery, verification, and personalization for an existing company set."
5132
- }
5133
- ];
5134
- function buildListConfig(template, targetDescription) {
5135
- const qualification = {
5136
- targetDescription,
5137
- minReviewCount: 5,
5138
- minRating: 4,
5139
- excludeFranchises: true,
5140
- customRules: ""
5141
- };
5142
- const personalization = {
5143
- industryContext: targetDescription,
5144
- emailBody: "",
5145
- creativeDirection: "",
5146
- exclusionRules: []
5147
- };
5148
- switch (template) {
5149
- case "full_pipeline":
5150
- return {
5151
- qualification,
5152
- enrichment: {
5153
- emailDiscovery: { primary: "tomba" },
5154
- emailVerification: { provider: "millionverifier", threshold: "ok" }
5155
- },
5156
- personalization,
5157
- pipeline: {
5158
- steps: [
5159
- {
5160
- key: "scrape",
5161
- label: "Scrape Companies",
5162
- resourceId: "lgn-01a-google-maps-scrape-workflow",
5163
- inputTemplate: {},
5164
- enabled: true,
5165
- order: 1
5166
- },
5167
- {
5168
- key: "acquire",
5169
- label: "Import Companies",
5170
- resourceId: "lgn-01b-apify-acquire-workflow",
5171
- inputTemplate: {},
5172
- enabled: true,
5173
- order: 2
5174
- },
5175
- {
5176
- key: "extract",
5177
- label: "Extract Website Data",
5178
- resourceId: "lgn-02-website-extract-workflow",
5179
- inputTemplate: {},
5180
- enabled: true,
5181
- order: 3
5182
- },
5183
- {
5184
- key: "qualify",
5185
- label: "Qualify Companies",
5186
- resourceId: "lgn-03-company-qualification-workflow",
5187
- inputTemplate: {},
5188
- enabled: true,
5189
- order: 4
5190
- },
5191
- {
5192
- key: "discover",
5193
- label: "Discover Emails",
5194
- resourceId: "lgn-04-email-discovery-workflow",
5195
- inputTemplate: {},
5196
- enabled: true,
5197
- order: 5
5198
- },
5199
- {
5200
- key: "verify",
5201
- label: "Verify Emails",
5202
- resourceId: "lgn-05-email-verification-workflow",
5203
- inputTemplate: {},
5204
- enabled: true,
5205
- order: 6
5206
- },
5207
- {
5208
- key: "personalize",
5209
- label: "Personalize Outreach",
5210
- resourceId: "ist-personalization-workflow",
5211
- inputTemplate: {},
5212
- enabled: true,
5213
- order: 7
5214
- }
5215
- ]
5216
- }
5217
- };
5218
- case "personalize_only":
5219
- return {
5220
- qualification,
5221
- personalization,
5222
- pipeline: {
5223
- steps: [
5224
- {
5225
- key: "personalize",
5226
- label: "Personalize Outreach",
5227
- resourceId: "ist-personalization-workflow",
5228
- inputTemplate: {},
5229
- enabled: true,
5230
- order: 1
5231
- },
5232
- {
5233
- key: "upload",
5234
- label: "Upload Contacts",
5235
- resourceId: "ist-upload-contacts-workflow",
5236
- inputTemplate: { requireOpeningLine: true },
5237
- enabled: true,
5238
- order: 2
5239
- }
5240
- ]
5241
- }
5242
- };
5243
- case "email_refresh":
5244
- return {
5245
- qualification,
5246
- enrichment: {
5247
- emailDiscovery: { primary: "tomba" },
5248
- emailVerification: { provider: "millionverifier", threshold: "ok" }
5249
- },
5250
- personalization,
5251
- pipeline: {
5252
- steps: [
5253
- {
5254
- key: "discover",
5255
- label: "Discover Emails",
5256
- resourceId: "lgn-04-email-discovery-workflow",
5257
- inputTemplate: {},
5258
- enabled: true,
5259
- order: 1
5260
- },
5261
- {
5262
- key: "verify",
5263
- label: "Verify Emails",
5264
- resourceId: "lgn-05-email-verification-workflow",
5265
- inputTemplate: {},
5266
- enabled: true,
5267
- order: 2
5268
- },
5269
- {
5270
- key: "personalize",
5271
- label: "Personalize Outreach",
5272
- resourceId: "ist-personalization-workflow",
5273
- inputTemplate: {},
5274
- enabled: true,
5275
- order: 3
5276
- }
5277
- ]
5278
- }
5279
- };
5280
- case "blank":
5281
- default:
5282
- return {
5283
- qualification,
5284
- pipeline: {
5285
- steps: []
5286
- }
5287
- };
5288
- }
5289
- }
5290
- function useDeleteLists() {
5291
- const { apiRequest, organizationId } = useElevasisServices();
5292
- const queryClient = useQueryClient();
5293
- return useMutation({
5294
- mutationFn: async (listIds) => {
5295
- const uniqueIds = [...new Set(listIds)].filter(Boolean);
5296
- if (uniqueIds.length === 0) return;
5297
- await Promise.all(
5298
- uniqueIds.map(
5299
- (listId) => apiRequest(`/acquisition/lists/${listId}`, {
5300
- method: "DELETE"
5301
- })
5302
- )
5303
- );
5304
- },
5305
- onSuccess: () => {
5306
- queryClient.invalidateQueries({ queryKey: acquisitionListKeys.list(organizationId) });
5307
- queryClient.invalidateQueries({ queryKey: acquisitionListKeys.telemetry(organizationId) });
5308
- showSuccessNotification("Lists deleted");
5309
- },
5310
- onError: (error) => {
5311
- showApiErrorNotification(error);
5312
- }
5313
- });
5314
- }
5315
- var EM_DASH = "\u2014";
5316
- function computeCompletionRatio(populated, personalized) {
5317
- if (populated === 0) return null;
5318
- return personalized / populated;
5319
- }
5320
- function formatPercentage(ratio, fractionDigits = 0) {
5321
- if (ratio == null) return EM_DASH;
5322
- return `${(ratio * 100).toFixed(fractionDigits)}%`;
5323
- }
5324
- function computeCompletionPercentage(populated, personalized) {
5325
- return formatPercentage(computeCompletionRatio(populated, personalized));
5326
- }
5327
- function computeBounceRate(d) {
5328
- const denominator = d.valid + d.risky + d.invalid + d.bounced;
5329
- if (denominator === 0) return EM_DASH;
5330
- return `${(d.bounced / denominator * 100).toFixed(1)}%`;
5331
- }
5332
- function computeBacklog(current, completed) {
5333
- return Math.max(current - completed, 0);
5334
- }
5335
- function getOverviewStatus(list) {
5336
- if ((list.activeWorkflows?.length ?? 0) > 0) {
5337
- return { label: "Active Work", color: "green" };
5338
- }
5339
- if (list.stageCounts.personalized > 0 && list.stageCounts.personalized === list.stageCounts.uploaded) {
5340
- return { label: "Complete", color: "blue" };
5341
- }
5342
- return { label: "Idle", color: "gray" };
5343
- }
5344
- function getNextFocus(list) {
5345
- if ((list.activeWorkflows?.length ?? 0) > 0) {
5346
- return { label: "Workflow running", count: list.activeWorkflows?.length ?? 0 };
5347
- }
5348
- if (list.totalCompanies === 0 && list.totalContacts === 0) {
5349
- return { label: "Populate list", count: null };
5350
- }
5351
- const extractGap = computeBacklog(list.stageCounts.populated, list.stageCounts.extracted);
5352
- if (extractGap > 0) {
5353
- return { label: "Extract website data", count: extractGap };
5354
- }
5355
- const qualifyGap = computeBacklog(list.stageCounts.extracted, list.stageCounts.qualified);
5356
- if (qualifyGap > 0) {
5357
- return { label: "Qualify companies", count: qualifyGap };
5358
- }
5359
- const discoverGap = computeBacklog(list.stageCounts.qualified, list.stageCounts.discovered);
5360
- if (discoverGap > 0) {
5361
- return { label: "Discover contacts", count: discoverGap };
5362
- }
5363
- const verifyGap = computeBacklog(list.stageCounts.discovered, list.stageCounts.verified);
5364
- if (verifyGap > 0) {
5365
- return { label: "Verify emails", count: verifyGap };
5366
- }
5367
- const personalizeGap = computeBacklog(list.stageCounts.verified, list.stageCounts.personalized);
5368
- if (personalizeGap > 0) {
5369
- return { label: "Personalize outreach", count: personalizeGap };
5370
- }
5371
- const uploadGap = computeBacklog(list.stageCounts.personalized, list.stageCounts.uploaded);
5372
- if (uploadGap > 0) {
5373
- return { label: "Upload contacts", count: uploadGap };
5374
- }
5375
- return { label: "Complete", count: null };
5376
- }
5377
- function formatCountLabel(value, noun) {
5378
- return `${value} ${noun}${value === 1 ? "" : "s"}`;
5379
- }
5380
- function getPrimaryAction({
5381
- uploadBacklog,
5382
- personalizationBacklog,
5383
- verificationBacklog,
5384
- deliverabilityRiskCount,
5385
- activeListCount,
5386
- totalLists
5387
- }) {
5388
- if (uploadBacklog > 0) {
5389
- return {
5390
- title: `Upload ${formatCountLabel(uploadBacklog, "personalized contact")}`,
5391
- detail: "No verification blockers are left on the contacts already ready for outreach.",
5392
- buttonLabel: "Review lists",
5393
- buttonTo: "/lead-gen/lists",
5394
- tone: "blue"
5395
- };
5396
- }
5397
- if (personalizationBacklog > 0) {
5398
- return {
5399
- title: `Personalize ${formatCountLabel(personalizationBacklog, "verified contact")}`,
5400
- detail: "Verification is done. The next bottleneck is drafting outreach copy.",
5401
- buttonLabel: "Open lists",
5402
- buttonTo: "/lead-gen/lists",
5403
- tone: "blue"
5404
- };
5405
- }
5406
- if (verificationBacklog > 0) {
5407
- return {
5408
- title: `Verify ${formatCountLabel(verificationBacklog, "discovered contact")}`,
5409
- detail: "Email validation is the main blocker before personalization can continue.",
5410
- buttonLabel: "Open lists",
5411
- buttonTo: "/lead-gen/lists",
5412
- tone: "orange"
5413
- };
5414
- }
5415
- if (deliverabilityRiskCount > 0) {
5416
- return {
5417
- title: `Resolve ${formatCountLabel(deliverabilityRiskCount, "deliverability risk")}`,
5418
- detail: "Risky, invalid, or bounced records are the main campaign health issue right now.",
5419
- buttonLabel: "View deliverability",
5420
- buttonTo: "/lead-gen/deliverability",
5421
- tone: "orange"
5422
- };
5423
- }
5424
- if (activeListCount > 0) {
5425
- return {
5426
- title: `Monitor ${formatCountLabel(activeListCount, "active list")}`,
5427
- detail: "Workflows are running and there are no immediate contact-stage backlogs to clear.",
5428
- buttonLabel: "Open lists",
5429
- buttonTo: "/lead-gen/lists",
5430
- tone: "green"
5431
- };
5432
- }
5433
- if (totalLists > 0) {
5434
- return {
5435
- title: "No blockers right now",
5436
- detail: "Lists are idle and the current pipeline does not show an urgent follow-up step.",
5437
- buttonLabel: "Review lists",
5438
- buttonTo: "/lead-gen/lists",
5439
- tone: "gray"
5440
- };
5441
- }
5442
- return {
5443
- title: "Create your first list",
5444
- detail: "Lead gen telemetry will appear here once a list starts moving through the pipeline.",
5445
- buttonLabel: "Open lists",
5446
- buttonTo: "/lead-gen/lists",
5447
- tone: "gray"
5448
- };
5449
- }
5450
- function CompactPipelineStage({
5451
- label,
5452
- value,
5453
- ratio
5454
- }) {
5455
- return /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "sm", children: /* @__PURE__ */ jsxs(Stack, { gap: 6, children: [
5456
- /* @__PURE__ */ jsxs(Group, { justify: "space-between", gap: "xs", wrap: "nowrap", children: [
5457
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", tt: "uppercase", fw: 600, children: label }),
5458
- /* @__PURE__ */ jsx(Badge, { size: "xs", variant: "light", color: "blue", children: formatPercentage(ratio) })
5459
- ] }),
5460
- /* @__PURE__ */ jsx(Title, { order: 4, children: value }),
5461
- /* @__PURE__ */ jsx(Progress, { value: ratio == null ? 0 : ratio * 100, size: "sm", radius: "xl", color: "blue" })
5462
- ] }) });
5463
- }
5464
- function LeadGenOverviewPage() {
5465
- const telemetryQuery = useListsTelemetry();
5466
- const listsQuery = useLists();
5467
- const data = telemetryQuery.data;
5468
- const listMetaById = new Map((listsQuery.data ?? []).map((list) => [list.id, list]));
5469
- const totalCompanies = data?.reduce((sum, list) => sum + list.totalCompanies, 0) ?? 0;
5470
- const totalContacts = data?.reduce((sum, list) => sum + list.totalContacts, 0) ?? 0;
5471
- const stageTotals = data?.reduce(
5472
- (acc, list) => ({
5473
- populated: acc.populated + list.stageCounts.populated,
5474
- extracted: acc.extracted + list.stageCounts.extracted,
5475
- qualified: acc.qualified + list.stageCounts.qualified,
5476
- discovered: acc.discovered + list.stageCounts.discovered,
5477
- verified: acc.verified + list.stageCounts.verified,
5478
- personalized: acc.personalized + list.stageCounts.personalized,
5479
- uploaded: acc.uploaded + list.stageCounts.uploaded
5480
- }),
5481
- { populated: 0, extracted: 0, qualified: 0, discovered: 0, verified: 0, personalized: 0, uploaded: 0 }
5482
- ) ?? { populated: 0, extracted: 0, qualified: 0, discovered: 0, verified: 0, personalized: 0, uploaded: 0 };
5483
- const deliverabilityTotals = data?.reduce(
5484
- (acc, list) => ({
5485
- valid: acc.valid + list.deliverability.valid,
5486
- risky: acc.risky + list.deliverability.risky,
5487
- invalid: acc.invalid + list.deliverability.invalid,
5488
- unknown: acc.unknown + list.deliverability.unknown,
5489
- bounced: acc.bounced + list.deliverability.bounced
5490
- }),
5491
- { valid: 0, risky: 0, invalid: 0, unknown: 0, bounced: 0 }
5492
- ) ?? { risky: 0, invalid: 0, bounced: 0 };
5493
- const activeListCount = data?.filter((list) => (list.activeWorkflows?.length ?? 0) > 0).length ?? 0;
5494
- const verificationBacklog = computeBacklog(stageTotals.discovered, stageTotals.verified);
5495
- const personalizationBacklog = computeBacklog(stageTotals.verified, stageTotals.personalized);
5496
- const uploadBacklog = computeBacklog(stageTotals.personalized, stageTotals.uploaded);
5497
- const deliverabilityRiskCount = deliverabilityTotals.risky + deliverabilityTotals.invalid + deliverabilityTotals.bounced;
5498
- const totalLists = data?.length ?? 0;
5499
- const summaryLine = [
5500
- `${totalLists} ${totalLists === 1 ? "list" : "lists"} active`,
5501
- `${formatCountLabel(uploadBacklog, "contact")} ready for upload`,
5502
- deliverabilityRiskCount === 0 ? "no risks" : `${formatCountLabel(deliverabilityRiskCount, "risk")}`
5503
- ].join(" \u2022 ");
5504
- const primaryAction = getPrimaryAction({
5505
- uploadBacklog,
5506
- personalizationBacklog,
5507
- verificationBacklog,
5508
- deliverabilityRiskCount,
5509
- activeListCount,
5510
- totalLists
5511
- });
5512
- const pipelineStages = [
5513
- {
5514
- label: "Populated",
5515
- value: stageTotals.populated,
5516
- ratio: totalCompanies === 0 ? null : stageTotals.populated / totalCompanies
5517
- },
5518
- {
5519
- label: "Qualified",
5520
- value: stageTotals.qualified,
5521
- ratio: totalCompanies === 0 ? null : stageTotals.qualified / totalCompanies
5522
- },
5523
- {
5524
- label: "Verified",
5525
- value: stageTotals.verified,
5526
- ratio: totalContacts === 0 ? null : stageTotals.verified / totalContacts
5527
- },
5528
- {
5529
- label: "Personalized",
5530
- value: stageTotals.personalized,
5531
- ratio: totalContacts === 0 ? null : stageTotals.personalized / totalContacts
5532
- },
5533
- {
5534
- label: "Uploaded",
5535
- value: stageTotals.uploaded,
5536
- ratio: totalContacts === 0 ? null : stageTotals.uploaded / totalContacts
5537
- }
5538
- ];
5539
- const overviewRows = data?.map((list) => {
5540
- const listMeta = listMetaById.get(list.listId);
5541
- const completionRatio = computeCompletionRatio(list.stageCounts.populated, list.stageCounts.personalized);
5542
- const nextFocus = getNextFocus(list);
5543
- return {
5544
- ...list,
5545
- name: listMeta?.name ?? `List ${list.listId.slice(0, 8)}`,
5546
- completionRatio,
5547
- nextFocus,
5548
- status: getOverviewStatus(list)
5549
- };
5550
- }).sort((a, b) => {
5551
- const aActive = a.status.label === "Active Work" ? 1 : 0;
5552
- const bActive = b.status.label === "Active Work" ? 1 : 0;
5553
- if (aActive !== bActive) return bActive - aActive;
5554
- const aBacklog = computeBacklog(a.stageCounts.discovered, a.stageCounts.verified) + computeBacklog(a.stageCounts.verified, a.stageCounts.personalized) + computeBacklog(a.stageCounts.personalized, a.stageCounts.uploaded);
5555
- const bBacklog = computeBacklog(b.stageCounts.discovered, b.stageCounts.verified) + computeBacklog(b.stageCounts.verified, b.stageCounts.personalized) + computeBacklog(b.stageCounts.personalized, b.stageCounts.uploaded);
5556
- if (aBacklog !== bBacklog) return bBacklog - aBacklog;
5557
- return b.totalContacts - a.totalContacts;
5558
- }) ?? [];
5559
- return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsx(PageContainer, { children: /* @__PURE__ */ jsxs(Stack, { children: [
5560
- /* @__PURE__ */ jsx(
5561
- PageTitleCaption,
5562
- {
5563
- title: "Lead Gen Overview",
5564
- caption: summaryLine,
5565
- rightSection: /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
5566
- /* @__PURE__ */ jsx(Button, { component: Link$1, to: "/lead-gen/lists", size: "sm", variant: "light", children: "Lists" }),
5567
- /* @__PURE__ */ jsx(Button, { component: Link$1, to: "/lead-gen/deliverability", size: "sm", variant: "light", children: "Deliverability" })
5568
- ] })
5569
- }
5570
- ),
5571
- 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: [
5572
- /* @__PURE__ */ jsxs(SimpleGrid, { cols: { base: 1, sm: 3 }, children: [
5573
- /* @__PURE__ */ jsx(StatCard, { variant: "hero", icon: IconPlayerPlay, value: activeListCount, label: "Active Lists" }),
5574
- /* @__PURE__ */ jsx(StatCard, { variant: "hero", icon: IconArrowRight, value: uploadBacklog, label: "Ready Contacts" }),
5575
- /* @__PURE__ */ jsx(StatCard, { variant: "hero", icon: IconAlertCircle, value: deliverabilityRiskCount, label: "At Risk" })
5576
- ] }),
5577
- /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
5578
- /* @__PURE__ */ jsxs(Group, { justify: "space-between", align: "flex-start", gap: "md", children: [
5579
- /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
5580
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", tt: "uppercase", fw: 700, children: "Next Action" }),
5581
- /* @__PURE__ */ jsx(Title, { order: 3, children: primaryAction.title }),
5582
- /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", maw: 620, children: primaryAction.detail })
5583
- ] }),
5584
- /* @__PURE__ */ jsx(Button, { component: Link$1, to: primaryAction.buttonTo, size: "sm", variant: "light", children: primaryAction.buttonLabel })
5585
- ] }),
5586
- /* @__PURE__ */ jsxs(SimpleGrid, { cols: { base: 1, md: 3 }, children: [
5587
- /* @__PURE__ */ jsxs(Paper, { withBorder: true, p: "sm", children: [
5588
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", tt: "uppercase", fw: 600, children: "Verification" }),
5589
- /* @__PURE__ */ jsxs(Group, { justify: "space-between", align: "flex-end", mt: 6, children: [
5590
- /* @__PURE__ */ jsx(Title, { order: 4, children: verificationBacklog }),
5591
- /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "waiting" })
5592
- ] })
5593
- ] }),
5594
- /* @__PURE__ */ jsxs(Paper, { withBorder: true, p: "sm", children: [
5595
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", tt: "uppercase", fw: 600, children: "Personalization" }),
5596
- /* @__PURE__ */ jsxs(Group, { justify: "space-between", align: "flex-end", mt: 6, children: [
5597
- /* @__PURE__ */ jsx(Title, { order: 4, children: personalizationBacklog }),
5598
- /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "ready" })
5599
- ] })
5600
- ] }),
5601
- /* @__PURE__ */ jsxs(Paper, { withBorder: true, p: "sm", children: [
5602
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", tt: "uppercase", fw: 600, children: "Completion" }),
5603
- /* @__PURE__ */ jsxs(Group, { justify: "space-between", align: "flex-end", mt: 6, children: [
5604
- /* @__PURE__ */ jsx(Title, { order: 4, children: computeCompletionPercentage(stageTotals.populated, stageTotals.personalized) }),
5605
- /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "personalized" })
5606
- ] })
5607
- ] })
5608
- ] })
5609
- ] }) }),
5610
- /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
5611
- /* @__PURE__ */ jsxs(Group, { justify: "space-between", align: "flex-end", children: [
5612
- /* @__PURE__ */ jsxs("div", { children: [
5613
- /* @__PURE__ */ jsx(Title, { order: 3, children: "Pipeline" }),
5614
- /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "Keep the funnel visible without giving every zero state its own section." })
5615
- ] }),
5616
- /* @__PURE__ */ jsxs(Badge, { variant: "light", color: "blue", children: [
5617
- stageTotals.extracted,
5618
- " extracted \u2022 ",
5619
- stageTotals.discovered,
5620
- " discovered"
5621
- ] })
5622
- ] }),
5623
- /* @__PURE__ */ jsx(SimpleGrid, { cols: { base: 1, sm: 2, xl: 5 }, children: pipelineStages.map((stage) => /* @__PURE__ */ jsx(
5624
- CompactPipelineStage,
5625
- {
5626
- label: stage.label,
5627
- value: stage.value,
5628
- ratio: stage.ratio
5629
- },
5630
- stage.label
5631
- )) })
5632
- ] }) }),
5633
- /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
5634
- /* @__PURE__ */ jsxs(Group, { justify: "space-between", align: "flex-end", children: [
5635
- /* @__PURE__ */ jsxs("div", { children: [
5636
- /* @__PURE__ */ jsx(Title, { order: 3, children: "List Snapshot" }),
5637
- /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "Ranked to surface active work first, then the largest unresolved backlogs." })
5638
- ] }),
5639
- /* @__PURE__ */ jsx(Button, { component: Link$1, to: "/lead-gen/lists", size: "sm", variant: "subtle", children: "Open lists" })
5640
- ] }),
5641
- /* @__PURE__ */ jsxs(Table, { children: [
5642
- /* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
5643
- /* @__PURE__ */ jsx(Table.Th, { children: "List" }),
5644
- /* @__PURE__ */ jsx(Table.Th, { children: "Companies" }),
5645
- /* @__PURE__ */ jsx(Table.Th, { children: "Contacts" }),
5646
- /* @__PURE__ */ jsx(Table.Th, { children: "Next Focus" }),
5647
- /* @__PURE__ */ jsx(Table.Th, { children: "Progress" }),
5648
- /* @__PURE__ */ jsx(Table.Th, { children: "Status" })
5649
- ] }) }),
5650
- /* @__PURE__ */ jsx(Table.Tbody, { children: overviewRows.map((list) => /* @__PURE__ */ jsxs(Table.Tr, { children: [
5651
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
5652
- /* @__PURE__ */ jsx(Link$1, { 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 }) }),
5653
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: list.listId })
5654
- ] }) }),
5655
- /* @__PURE__ */ jsx(Table.Td, { children: list.totalCompanies }),
5656
- /* @__PURE__ */ jsx(Table.Td, { children: list.totalContacts }),
5657
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
5658
- /* @__PURE__ */ jsx(Text, { size: "sm", fw: 500, children: list.nextFocus.label }),
5659
- /* @__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` })
5660
- ] }) }),
5661
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsxs(Stack, { gap: "xs", miw: 150, children: [
5662
- /* @__PURE__ */ jsxs(Group, { justify: "space-between", gap: "xs", children: [
5663
- /* @__PURE__ */ jsx(Text, { size: "sm", children: formatPercentage(list.completionRatio) }),
5664
- /* @__PURE__ */ jsxs(Text, { size: "xs", c: "dimmed", children: [
5665
- list.stageCounts.personalized,
5666
- "/",
5667
- list.stageCounts.populated || 0
5668
- ] })
5669
- ] }),
5670
- /* @__PURE__ */ jsx(
5671
- Progress,
5672
- {
5673
- value: list.completionRatio == null ? 0 : list.completionRatio * 100,
5674
- size: "sm",
5675
- radius: "xl",
5676
- color: "blue"
5677
- }
5678
- )
5679
- ] }) }),
5680
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Badge, { size: "sm", variant: "light", color: list.status.color, children: list.status.label }) })
5681
- ] }, list.listId)) })
5682
- ] })
5683
- ] }) })
5684
- ] })
5685
- ] }) }) });
5686
- }
5687
- function LeadGenDeliverabilityPage() {
5688
- const { data, isLoading, error } = useListsTelemetry();
5689
- if (isLoading) {
5690
- return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsx(PageContainer, { children: /* @__PURE__ */ jsxs(Stack, { children: [
5691
- /* @__PURE__ */ jsx(
5692
- PageTitleCaption,
5693
- {
5694
- title: "Deliverability",
5695
- caption: "Email validity breakdown and bounce health across lists"
5696
- }
5697
- ),
5698
- /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsx(Center, { p: "xl", children: /* @__PURE__ */ jsx(Loader, {}) }) })
5699
- ] }) }) });
5700
- }
5701
- if (error) {
5702
- return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsx(PageContainer, { children: /* @__PURE__ */ jsxs(Stack, { children: [
5703
- /* @__PURE__ */ jsx(
5704
- PageTitleCaption,
5705
- {
5706
- title: "Deliverability",
5707
- caption: "Email validity breakdown and bounce health across lists"
5708
- }
5709
- ),
5710
- /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsx(CenteredErrorState, { error, title: "Failed to load deliverability data" }) })
5711
- ] }) }) });
5712
- }
5713
- if (data?.length === 0) {
5714
- return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsx(PageContainer, { children: /* @__PURE__ */ jsxs(Stack, { children: [
5715
- /* @__PURE__ */ jsx(
5716
- PageTitleCaption,
5717
- {
5718
- title: "Deliverability",
5719
- caption: "Email validity breakdown and bounce health across lists"
5720
- }
5721
- ),
5722
- /* @__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." }) }) })
5723
- ] }) }) });
5724
- }
5725
- const lists = data ?? [];
5726
- const totals = lists.reduce(
5727
- (acc, list) => ({
5728
- valid: acc.valid + list.deliverability.valid,
5729
- risky: acc.risky + list.deliverability.risky,
5730
- invalid: acc.invalid + list.deliverability.invalid,
5731
- unknown: acc.unknown + list.deliverability.unknown,
5732
- bounced: acc.bounced + list.deliverability.bounced
5733
- }),
5734
- { valid: 0, risky: 0, invalid: 0, unknown: 0, bounced: 0 }
5735
- );
5736
- const summaryTiles = [
5737
- { label: "Valid", value: totals.valid, icon: IconMailCheck },
5738
- { label: "Risky", value: totals.risky, icon: IconAlertCircle },
5739
- { label: "Invalid", value: totals.invalid, icon: IconX },
5740
- { label: "Unknown", value: totals.unknown, icon: IconQuestionMark },
5741
- { label: "Bounced", value: totals.bounced, icon: IconAlertCircle },
5742
- { label: "Bounce Rate", value: computeBounceRate(totals), icon: IconChecklist }
5743
- ];
5744
- return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsx(PageContainer, { children: /* @__PURE__ */ jsxs(Stack, { children: [
5745
- /* @__PURE__ */ jsx(
5746
- PageTitleCaption,
5747
- {
5748
- title: "Deliverability",
5749
- caption: "Email validity breakdown and bounce health across lists"
5750
- }
5751
- ),
5752
- /* @__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)) }),
5753
- /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsxs(Table, { children: [
5754
- /* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
5755
- /* @__PURE__ */ jsx(Table.Th, { children: "List ID" }),
5756
- /* @__PURE__ */ jsx(Table.Th, { children: "Valid" }),
5757
- /* @__PURE__ */ jsx(Table.Th, { children: "Risky" }),
5758
- /* @__PURE__ */ jsx(Table.Th, { children: "Invalid" }),
5759
- /* @__PURE__ */ jsx(Table.Th, { children: "Unknown" }),
5760
- /* @__PURE__ */ jsx(Table.Th, { children: "Bounced" }),
5761
- /* @__PURE__ */ jsx(Table.Th, { children: "Bounce Rate" })
5762
- ] }) }),
5763
- /* @__PURE__ */ jsx(Table.Tbody, { children: lists.map((list) => /* @__PURE__ */ jsxs(Table.Tr, { children: [
5764
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", fw: 500, children: list.listId }) }),
5765
- /* @__PURE__ */ jsx(Table.Td, { children: list.deliverability.valid }),
5766
- /* @__PURE__ */ jsx(Table.Td, { children: list.deliverability.risky }),
5767
- /* @__PURE__ */ jsx(Table.Td, { children: list.deliverability.invalid }),
5768
- /* @__PURE__ */ jsx(Table.Td, { children: list.deliverability.unknown }),
5769
- /* @__PURE__ */ jsx(Table.Td, { children: list.deliverability.bounced }),
5770
- /* @__PURE__ */ jsx(Table.Td, { children: computeBounceRate(list.deliverability) })
5771
- ] }, list.listId)) })
5772
- ] }) })
5773
- ] }) }) });
5774
- }
5775
- var PAGE_SIZE_DEFAULT2 = 20;
5776
- function LeadGenListsPage() {
5777
- const navigate = useNavigate();
5778
- const [searchQuery, setSearchQuery] = useState("");
5779
- const [showCreateList, setShowCreateList] = useState(false);
5780
- const [newListName, setNewListName] = useState("");
5781
- const [newListDescription, setNewListDescription] = useState("");
5782
- const [selectedTemplate, setSelectedTemplate] = useState("full_pipeline");
5783
- const [showBatchDelete, setShowBatchDelete] = useState(false);
5784
- const { data: lists, isLoading: listsLoading } = useLists();
5785
- const { data: telemetry, isLoading: telemetryLoading } = useListsTelemetry();
5786
- const createListMutation = useCreateList();
5787
- const deleteListsMutation = useDeleteLists();
5788
- const { sort, toggleSort } = useTableSort("created");
5789
- const contactCountByListId = useMemo(
5790
- () => new Map((telemetry ?? []).map((item) => [item.listId, item.totalContacts])),
5791
- [telemetry]
5792
- );
5793
- const sortAccessors2 = useMemo(
5794
- () => ({
5795
- name: (list) => list.name,
5796
- description: (list) => list.description || "",
5797
- contacts: (list) => contactCountByListId.get(list.id) ?? 0,
5798
- created: (list) => list.createdAt
5799
- }),
5800
- [contactCountByListId]
5801
- );
5802
- const filteredLists = useMemo(() => {
5803
- if (!lists) return [];
5804
- if (!searchQuery.trim()) return lists;
5805
- const query = searchQuery.toLowerCase();
5806
- return lists.filter((list) => list.name.toLowerCase().includes(query));
5807
- }, [lists, searchQuery]);
5808
- const sortedLists = useMemo(() => sortData(filteredLists, sort, sortAccessors2), [filteredLists, sort, sortAccessors2]);
5809
- const pagination = usePaginationState(PAGE_SIZE_DEFAULT2, [searchQuery], sortedLists.length);
5810
- const paginatedLists = useMemo(
5811
- () => sortedLists.slice(pagination.offset, pagination.offset + PAGE_SIZE_DEFAULT2),
5812
- [sortedLists, pagination.offset]
5813
- );
5814
- const selection = useTableSelection(paginatedLists, sortedLists);
5815
- const selectedTemplateMeta = LIST_TEMPLATE_OPTIONS.find((option) => option.value === selectedTemplate);
5816
- function resetCreateListModal() {
5817
- setNewListName("");
5818
- setNewListDescription("");
5819
- setSelectedTemplate("full_pipeline");
5820
- setShowCreateList(false);
5821
- }
5822
- function handleCreateList() {
5823
- const trimmedName = newListName.trim();
5824
- if (!trimmedName) return;
5825
- const body = {
5826
- name: trimmedName,
5827
- description: newListDescription.trim() || null,
5828
- type: selectedTemplate,
5829
- config: buildListConfig(selectedTemplate, trimmedName)
5830
- };
5831
- createListMutation.mutate(body, {
5832
- onSuccess: (list) => {
5833
- resetCreateListModal();
5834
- navigate({ to: "/lead-gen/lists/$listId", params: { listId: list.id } });
5835
- }
5836
- });
5837
- }
5838
- return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsxs(PageContainer, { children: [
5839
- /* @__PURE__ */ jsx(Stack, { children: /* @__PURE__ */ jsx(
5840
- PageTitleCaption,
5841
- {
5842
- title: "Lists",
5843
- caption: "Lead lists and contact organization",
5844
- rightSection: /* @__PURE__ */ jsx(Button, { leftSection: /* @__PURE__ */ jsx(IconPlus, { size: 16 }), onClick: () => setShowCreateList(true), children: "New List" })
5845
- }
5846
- ) }),
5847
- /* @__PURE__ */ jsx(Paper, { children: /* @__PURE__ */ jsxs(Stack, { children: [
5848
- /* @__PURE__ */ jsx(
5849
- FilterBar,
5850
- {
5851
- actions: /* @__PURE__ */ jsx(
5852
- TableSelectionToolbar,
5853
- {
5854
- selectedCount: selection.selectedCount,
5855
- onDelete: () => setShowBatchDelete(true),
5856
- isDeleting: deleteListsMutation.isPending
5857
- }
5858
- ),
5859
- children: /* @__PURE__ */ jsx(
5860
- TextInput,
5861
- {
5862
- placeholder: "Search by name...",
5863
- leftSection: /* @__PURE__ */ jsx(IconSearch, { size: 16 }),
5864
- value: searchQuery,
5865
- onChange: (e) => setSearchQuery(e.currentTarget.value)
5866
- }
5867
- )
5868
- }
5869
- ),
5870
- listsLoading || telemetryLoading ? /* @__PURE__ */ jsx(Center, { p: "xl", children: /* @__PURE__ */ jsx(Loader, {}) }) : !filteredLists.length ? /* @__PURE__ */ jsx(
5871
- EmptyState,
5872
- {
5873
- icon: IconList,
5874
- title: searchQuery.trim() ? "No lists match your search" : "No lists yet",
5875
- description: searchQuery.trim() ? void 0 : "Create one to get started.",
5876
- action: searchQuery.trim() ? void 0 : {
5877
- label: "Create List",
5878
- onClick: () => setShowCreateList(true),
5879
- icon: /* @__PURE__ */ jsx(IconPlus, { size: 16 })
5880
- }
5881
- }
5882
- ) : /* @__PURE__ */ jsxs(Table, { children: [
5883
- /* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
5884
- /* @__PURE__ */ jsx(Table.Th, { w: 40, children: /* @__PURE__ */ jsx(
5885
- Checkbox,
5886
- {
5887
- checked: selection.isPageAllSelected,
5888
- indeterminate: selection.isPagePartiallySelected,
5889
- onChange: selection.togglePage
5890
- }
5891
- ) }),
5892
- /* @__PURE__ */ jsx(SortableHeader, { column: "name", sort, onToggle: toggleSort, children: "Name" }),
5893
- /* @__PURE__ */ jsx(SortableHeader, { column: "description", sort, onToggle: toggleSort, children: "Description" }),
5894
- /* @__PURE__ */ jsx(SortableHeader, { column: "contacts", sort, onToggle: toggleSort, children: "Contacts" }),
5895
- /* @__PURE__ */ jsx(SortableHeader, { column: "created", sort, onToggle: toggleSort, children: "Created" })
5896
- ] }) }),
5897
- /* @__PURE__ */ jsx(Table.Tbody, { children: paginatedLists.map((list) => /* @__PURE__ */ jsxs(
5898
- Table.Tr,
5899
- {
5900
- style: { cursor: "pointer" },
5901
- onClick: () => navigate({ to: "/lead-gen/lists/$listId", params: { listId: list.id } }),
5902
- children: [
5903
- /* @__PURE__ */ jsx(Table.Td, { onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx(Checkbox, { checked: selection.isSelected(list.id), onChange: () => selection.toggle(list.id) }) }),
5904
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { fw: 500, children: list.name }) }),
5905
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: list.description || "-" }) }),
5906
- /* @__PURE__ */ jsx(Table.Td, { children: contactCountByListId.get(list.id) ?? 0 }),
5907
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: formatDate3(list.createdAt) }) })
5908
- ]
5909
- },
5910
- list.id
5911
- )) })
5912
- ] }),
5913
- sortedLists.length > PAGE_SIZE_DEFAULT2 && /* @__PURE__ */ jsx(Group, { justify: "center", children: /* @__PURE__ */ jsx(
5914
- Pagination,
5915
- {
5916
- value: pagination.page,
5917
- onChange: pagination.setPage,
5918
- total: pagination.totalPages(sortedLists.length),
5919
- size: "sm"
5920
- }
5921
- ) })
5922
- ] }) }),
5923
- /* @__PURE__ */ jsx(
5924
- CustomModal,
5925
- {
5926
- opened: showCreateList,
5927
- onClose: () => !createListMutation.isPending && resetCreateListModal(),
5928
- size: "md",
5929
- loading: createListMutation.isPending,
5930
- children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
5931
- /* @__PURE__ */ jsxs(Group, { gap: "sm", children: [
5932
- /* @__PURE__ */ jsx(IconSparkles, { size: 24 }),
5933
- /* @__PURE__ */ jsx(Title, { order: 4, children: "Create List" })
5934
- ] }),
5935
- /* @__PURE__ */ jsx(
5936
- TextInput,
5937
- {
5938
- label: "List Name",
5939
- placeholder: "e.g. Orange County Vets Q2",
5940
- value: newListName,
5941
- onChange: (event) => setNewListName(event.currentTarget.value),
5942
- disabled: createListMutation.isPending,
5943
- required: true
5944
- }
5945
- ),
5946
- /* @__PURE__ */ jsx(
5947
- Textarea,
5948
- {
5949
- label: "Description",
5950
- placeholder: "Optional context for this list",
5951
- value: newListDescription,
5952
- onChange: (event) => setNewListDescription(event.currentTarget.value),
5953
- disabled: createListMutation.isPending,
5954
- minRows: 3
5955
- }
5956
- ),
5957
- /* @__PURE__ */ jsx(
5958
- Select,
5959
- {
5960
- label: "Pipeline Template",
5961
- data: LIST_TEMPLATE_OPTIONS.map((option) => ({
5962
- value: option.value,
5963
- label: option.label
5964
- })),
5965
- value: selectedTemplate,
5966
- onChange: (value) => setSelectedTemplate(value ?? "full_pipeline"),
5967
- disabled: createListMutation.isPending,
5968
- allowDeselect: false
5969
- }
5970
- ),
5971
- selectedTemplateMeta ? /* @__PURE__ */ jsx(Alert, { variant: "light", color: "blue", children: /* @__PURE__ */ jsx(Text, { size: "sm", children: selectedTemplateMeta.description }) }) : null,
5972
- /* @__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." }),
5973
- /* @__PURE__ */ jsxs(Group, { justify: "flex-end", children: [
5974
- /* @__PURE__ */ jsx(Button, { variant: "light", onClick: resetCreateListModal, disabled: createListMutation.isPending, children: "Cancel" }),
5975
- /* @__PURE__ */ jsx(Button, { onClick: handleCreateList, loading: createListMutation.isPending, disabled: !newListName.trim(), children: "Create List" })
5976
- ] })
5977
- ] })
5978
- }
5979
- ),
5980
- /* @__PURE__ */ jsx(
5981
- CustomModal,
5982
- {
5983
- opened: showBatchDelete,
5984
- onClose: () => !deleteListsMutation.isPending && setShowBatchDelete(false),
5985
- size: "sm",
5986
- loading: deleteListsMutation.isPending,
5987
- children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
5988
- /* @__PURE__ */ jsxs(Group, { gap: "sm", children: [
5989
- /* @__PURE__ */ jsx(IconAlertTriangle, { size: 24, color: "var(--color-error)" }),
5990
- /* @__PURE__ */ jsxs(Title, { order: 4, children: [
5991
- "Delete ",
5992
- selection.selectedCount,
5993
- " Lists"
5994
- ] })
5995
- ] }),
5996
- /* @__PURE__ */ jsxs(Text, { size: "sm", children: [
5997
- "Are you sure you want to delete",
5998
- " ",
5999
- /* @__PURE__ */ jsx(Text, { span: true, fw: 600, children: selection.selectedCount }),
6000
- " ",
6001
- "selected lists?"
6002
- ] }),
6003
- /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "This action cannot be undone." }),
6004
- /* @__PURE__ */ jsxs(Group, { justify: "flex-end", mt: "md", children: [
6005
- /* @__PURE__ */ jsx(
6006
- Button,
6007
- {
6008
- variant: "light",
6009
- onClick: () => setShowBatchDelete(false),
6010
- disabled: deleteListsMutation.isPending,
6011
- children: "Cancel"
6012
- }
6013
- ),
6014
- /* @__PURE__ */ jsx(
6015
- Button,
6016
- {
6017
- color: "red",
6018
- loading: deleteListsMutation.isPending,
6019
- onClick: () => {
6020
- deleteListsMutation.mutate([...selection.selectedIds], {
6021
- onSuccess: () => {
6022
- setShowBatchDelete(false);
6023
- selection.clear();
6024
- }
6025
- });
6026
- },
6027
- children: "Delete"
6028
- }
6029
- )
6030
- ] })
6031
- ] })
6032
- }
6033
- )
6034
- ] }) });
6035
- }
6036
- function formatDateTime2(value) {
6037
- if (!value) return "Not yet";
6038
- return new Date(value).toLocaleString("en-US", {
6039
- month: "short",
6040
- day: "numeric",
6041
- year: "numeric",
6042
- hour: "numeric",
6043
- minute: "2-digit"
6044
- });
6045
- }
6046
- function LeadGenListDetailPage({ listId }) {
6047
- const navigate = useNavigate();
6048
- const [selectedStepKey, setSelectedStepKey] = useState(null);
6049
- const listQuery = useList(listId);
6050
- const progressQuery = useListProgress(listId);
6051
- const executionsQuery = useListExecutions(listId);
6052
- const selectedStep = useMemo(
6053
- () => listQuery.data?.config.pipeline?.steps?.slice().sort((a, b) => a.order - b.order).find((step) => step.key === selectedStepKey) ?? null,
6054
- [listQuery.data?.config.pipeline?.steps, selectedStepKey]
6055
- );
6056
- const resourceDefinitionQuery = useResourceDefinition(selectedStep?.resourceId ?? "", !!selectedStep?.resourceId);
6057
- const isLoading = listQuery.isLoading || progressQuery.isLoading || executionsQuery.isLoading;
6058
- const error = listQuery.error ?? progressQuery.error ?? executionsQuery.error;
6059
- if (isLoading) {
6060
- return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsx(PageContainer, { children: /* @__PURE__ */ jsxs(Stack, { children: [
6061
- /* @__PURE__ */ jsx(
6062
- PageTitleCaption,
6063
- {
6064
- title: "List Detail",
6065
- caption: "Configuration, progress, and execution history for a single lead-gen list",
6066
- rightSection: /* @__PURE__ */ jsx(
6067
- Button,
6068
- {
6069
- variant: "light",
6070
- size: "sm",
6071
- leftSection: /* @__PURE__ */ jsx(IconArrowLeft, { size: 16 }),
6072
- onClick: () => navigate({ to: "/lead-gen/lists" }),
6073
- children: "Lists"
6074
- }
6075
- )
6076
- }
6077
- ),
6078
- /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsx(Center, { p: "xl", children: /* @__PURE__ */ jsx(Loader, {}) }) })
6079
- ] }) }) });
6080
- }
6081
- if (error) {
6082
- return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsx(PageContainer, { children: /* @__PURE__ */ jsxs(Stack, { children: [
6083
- /* @__PURE__ */ jsx(
6084
- PageTitleCaption,
6085
- {
6086
- title: "List Detail",
6087
- caption: "Configuration, progress, and execution history for a single lead-gen list",
6088
- rightSection: /* @__PURE__ */ jsx(
6089
- Button,
6090
- {
6091
- variant: "light",
6092
- size: "sm",
6093
- leftSection: /* @__PURE__ */ jsx(IconArrowLeft, { size: 16 }),
6094
- onClick: () => navigate({ to: "/lead-gen/lists" }),
6095
- children: "Lists"
6096
- }
6097
- )
6098
- }
6099
- ),
6100
- /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsx(CenteredErrorState, { error, title: "Failed to load list detail" }) })
6101
- ] }) }) });
6102
- }
6103
- if (!listQuery.data || !progressQuery.data) {
6104
- return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsx(PageContainer, { children: /* @__PURE__ */ jsxs(Stack, { children: [
6105
- /* @__PURE__ */ jsx(
6106
- PageTitleCaption,
6107
- {
6108
- title: "List Not Found",
6109
- caption: "The requested lead-gen list is unavailable.",
6110
- rightSection: /* @__PURE__ */ jsx(
6111
- Button,
6112
- {
6113
- variant: "light",
6114
- size: "sm",
6115
- leftSection: /* @__PURE__ */ jsx(IconArrowLeft, { size: 16 }),
6116
- onClick: () => navigate({ to: "/lead-gen/lists" }),
6117
- children: "Lists"
6118
- }
6119
- )
6120
- }
6121
- ),
6122
- /* @__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." }) }) })
6123
- ] }) }) });
6124
- }
6125
- const list = listQuery.data;
6126
- const progress = progressQuery.data;
6127
- const executions = executionsQuery.data ?? [];
6128
- const qualification = list.config.qualification;
6129
- const pipelineSteps = list.config.pipeline?.steps?.slice().sort((a, b) => a.order - b.order) ?? [];
6130
- const stageTiles = [
6131
- { label: "Populated", value: progress.stageCounts.populated },
6132
- { label: "Extracted", value: progress.stageCounts.extracted },
6133
- { label: "Qualified", value: progress.stageCounts.qualified },
6134
- { label: "Discovered", value: progress.stageCounts.discovered },
6135
- { label: "Verified", value: progress.stageCounts.verified },
6136
- { label: "Personalized", value: progress.stageCounts.personalized },
6137
- { label: "Uploaded", value: progress.stageCounts.uploaded }
6138
- ];
6139
- const deliverabilityTiles = [
6140
- { label: "Valid", value: progress.deliverability.valid },
6141
- { label: "Risky", value: progress.deliverability.risky },
6142
- { label: "Invalid", value: progress.deliverability.invalid },
6143
- { label: "Unknown", value: progress.deliverability.unknown },
6144
- { label: "Bounced", value: progress.deliverability.bounced }
6145
- ];
6146
- return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsxs(PageContainer, { children: [
6147
- /* @__PURE__ */ jsxs(Stack, { children: [
6148
- /* @__PURE__ */ jsx(
6149
- PageTitleCaption,
6150
- {
6151
- title: list.name,
6152
- caption: list.description ?? "Configuration, progress, and execution history for this list",
6153
- rightSection: /* @__PURE__ */ jsx(
6154
- Button,
6155
- {
6156
- variant: "light",
6157
- size: "sm",
6158
- leftSection: /* @__PURE__ */ jsx(IconArrowLeft, { size: 16 }),
6159
- onClick: () => navigate({ to: "/lead-gen/lists" }),
6160
- children: "Lists"
6161
- }
6162
- )
6163
- }
6164
- ),
6165
- /* @__PURE__ */ jsxs(Stack, { gap: "xs", children: [
6166
- /* @__PURE__ */ jsx(Badge, { size: "sm", variant: "light", color: getStatusColor3(list.status), children: list.status }),
6167
- /* @__PURE__ */ jsxs(Text, { size: "sm", c: "dimmed", children: [
6168
- "Created ",
6169
- formatDateTime2(list.createdAt)
6170
- ] })
6171
- ] }),
6172
- /* @__PURE__ */ jsxs(SimpleGrid, { cols: { base: 1, sm: 2, lg: 4 }, children: [
6173
- /* @__PURE__ */ jsxs(Paper, { withBorder: true, p: "md", children: [
6174
- /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", mb: "xs", children: "Companies" }),
6175
- /* @__PURE__ */ jsx(Title, { order: 3, children: progress.totalCompanies })
6176
- ] }),
6177
- /* @__PURE__ */ jsxs(Paper, { withBorder: true, p: "md", children: [
6178
- /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", mb: "xs", children: "Contacts" }),
6179
- /* @__PURE__ */ jsx(Title, { order: 3, children: progress.totalContacts })
6180
- ] }),
6181
- /* @__PURE__ */ jsxs(Paper, { withBorder: true, p: "md", children: [
6182
- /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", mb: "xs", children: "Pipeline Steps" }),
6183
- /* @__PURE__ */ jsx(Title, { order: 3, children: pipelineSteps.length })
6184
- ] }),
6185
- /* @__PURE__ */ jsxs(Paper, { withBorder: true, p: "md", children: [
6186
- /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", mb: "xs", children: "Executions" }),
6187
- /* @__PURE__ */ jsx(Title, { order: 3, children: executions.length })
6188
- ] })
6189
- ] }),
6190
- /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
6191
- /* @__PURE__ */ jsx(Title, { order: 3, children: "Pipeline Progress" }),
6192
- /* @__PURE__ */ jsx(SimpleGrid, { cols: { base: 2, sm: 3, lg: 7 }, children: stageTiles.map((tile) => /* @__PURE__ */ jsxs(Paper, { withBorder: true, p: "sm", children: [
6193
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: tile.label }),
6194
- /* @__PURE__ */ jsx(Title, { order: 4, children: tile.value })
6195
- ] }, tile.label)) })
6196
- ] }) }),
6197
- /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
6198
- /* @__PURE__ */ jsx(Title, { order: 3, children: "Deliverability" }),
6199
- /* @__PURE__ */ jsx(SimpleGrid, { cols: { base: 2, sm: 3, lg: 5 }, children: deliverabilityTiles.map((tile) => /* @__PURE__ */ jsxs(Paper, { withBorder: true, p: "sm", children: [
6200
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: tile.label }),
6201
- /* @__PURE__ */ jsx(Title, { order: 4, children: tile.value })
6202
- ] }, tile.label)) })
6203
- ] }) }),
6204
- /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
6205
- /* @__PURE__ */ jsx(Title, { order: 3, children: "Configuration" }),
6206
- /* @__PURE__ */ jsxs(SimpleGrid, { cols: { base: 1, lg: 2 }, children: [
6207
- /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "sm", children: /* @__PURE__ */ jsxs(Stack, { gap: "xs", children: [
6208
- /* @__PURE__ */ jsx(Text, { size: "sm", fw: 600, children: "Qualification" }),
6209
- qualification ? /* @__PURE__ */ jsxs(Fragment, { children: [
6210
- /* @__PURE__ */ jsxs(Text, { size: "sm", children: [
6211
- "Target: ",
6212
- qualification.targetDescription
6213
- ] }),
6214
- /* @__PURE__ */ jsxs(Text, { size: "sm", children: [
6215
- "Minimum reviews: ",
6216
- qualification.minReviewCount
6217
- ] }),
6218
- /* @__PURE__ */ jsxs(Text, { size: "sm", children: [
6219
- "Minimum rating: ",
6220
- qualification.minRating
6221
- ] }),
6222
- /* @__PURE__ */ jsxs(Text, { size: "sm", children: [
6223
- "Exclude franchises: ",
6224
- qualification.excludeFranchises ? "Yes" : "No"
6225
- ] })
6226
- ] }) : /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "Qualification settings are not configured for this list." })
6227
- ] }) }),
6228
- /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "sm", children: /* @__PURE__ */ jsxs(Stack, { gap: "xs", children: [
6229
- /* @__PURE__ */ jsx(Text, { size: "sm", fw: 600, children: "Enrichment + Personalization" }),
6230
- /* @__PURE__ */ jsxs(Text, { size: "sm", children: [
6231
- "Discovery provider: ",
6232
- list.config.enrichment?.emailDiscovery?.primary ?? "Inherited default"
6233
- ] }),
6234
- /* @__PURE__ */ jsxs(Text, { size: "sm", children: [
6235
- "Verification provider: ",
6236
- list.config.enrichment?.emailVerification?.provider ?? "Inherited default"
6237
- ] }),
6238
- /* @__PURE__ */ jsxs(Text, { size: "sm", children: [
6239
- "Industry context: ",
6240
- list.config.personalization?.industryContext ?? "Inherited default"
6241
- ] }),
6242
- /* @__PURE__ */ jsxs(Text, { size: "sm", children: [
6243
- "Creative direction: ",
6244
- list.config.personalization?.creativeDirection ?? "Inherited default"
6245
- ] })
6246
- ] }) })
6247
- ] }),
6248
- list.config.personalization?.emailBody ? /* @__PURE__ */ jsxs(Box, { children: [
6249
- /* @__PURE__ */ jsx(Text, { size: "sm", fw: 600, mb: "xs", children: "Email Body Template" }),
6250
- /* @__PURE__ */ jsx(Code, { block: true, children: list.config.personalization.emailBody })
6251
- ] }) : null
6252
- ] }) }),
6253
- /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
6254
- /* @__PURE__ */ jsx(Title, { order: 3, children: "Pipeline Steps" }),
6255
- !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: [
6256
- /* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
6257
- /* @__PURE__ */ jsx(Table.Th, { children: "Order" }),
6258
- /* @__PURE__ */ jsx(Table.Th, { children: "Step" }),
6259
- /* @__PURE__ */ jsx(Table.Th, { children: "Resource" }),
6260
- /* @__PURE__ */ jsx(Table.Th, { children: "Status" }),
6261
- /* @__PURE__ */ jsx(Table.Th, { children: "Action" })
6262
- ] }) }),
6263
- /* @__PURE__ */ jsx(Table.Tbody, { children: pipelineSteps.map((step) => /* @__PURE__ */ jsxs(Table.Tr, { children: [
6264
- /* @__PURE__ */ jsx(Table.Td, { children: step.order }),
6265
- /* @__PURE__ */ jsxs(Table.Td, { children: [
6266
- /* @__PURE__ */ jsx(Text, { fw: 500, children: step.label }),
6267
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: step.key })
6268
- ] }),
6269
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Code, { children: step.resourceId }) }),
6270
- /* @__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" }) }),
6271
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(
6272
- Button,
6273
- {
6274
- size: "xs",
6275
- variant: "light",
6276
- leftSection: /* @__PURE__ */ jsx(IconPlayerPlay, { size: 14 }),
6277
- disabled: !step.enabled,
6278
- onClick: () => setSelectedStepKey(step.key),
6279
- children: "Run"
6280
- }
6281
- ) })
6282
- ] }, step.key)) })
6283
- ] })
6284
- ] }) }),
6285
- /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
6286
- /* @__PURE__ */ jsx(Title, { order: 3, children: "Execution History" }),
6287
- !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: [
6288
- /* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
6289
- /* @__PURE__ */ jsx(Table.Th, { children: "Resource" }),
6290
- /* @__PURE__ */ jsx(Table.Th, { children: "Status" }),
6291
- /* @__PURE__ */ jsx(Table.Th, { children: "Started" }),
6292
- /* @__PURE__ */ jsx(Table.Th, { children: "Completed" }),
6293
- /* @__PURE__ */ jsx(Table.Th, { children: "Duration" })
6294
- ] }) }),
6295
- /* @__PURE__ */ jsx(Table.Tbody, { children: executions.map((execution) => /* @__PURE__ */ jsxs(Table.Tr, { children: [
6296
- /* @__PURE__ */ jsxs(Table.Td, { children: [
6297
- /* @__PURE__ */ jsx(Text, { fw: 500, children: execution.resourceId }),
6298
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: execution.executionId })
6299
- ] }),
6300
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Badge, { size: "sm", variant: "light", color: getStatusColor3(execution.status), children: execution.status }) }),
6301
- /* @__PURE__ */ jsx(Table.Td, { children: formatDateTime2(execution.createdAt) }),
6302
- /* @__PURE__ */ jsx(Table.Td, { children: formatDateTime2(execution.completedAt) }),
6303
- /* @__PURE__ */ jsx(Table.Td, { children: execution.durationMs == null ? "n/a" : `${execution.durationMs} ms` })
6304
- ] }, execution.executionId)) })
6305
- ] })
6306
- ] }) })
6307
- ] }),
6308
- selectedStep ? /* @__PURE__ */ jsx(
6309
- ResourceExecuteDialog,
6310
- {
6311
- opened: !!selectedStep && !resourceDefinitionQuery.isLoading,
6312
- onClose: () => setSelectedStepKey(null),
6313
- resource: {
6314
- resourceId: selectedStep.resourceId,
6315
- resourceType: "workflow",
6316
- name: selectedStep.label,
6317
- formSchema: resourceDefinitionQuery.data?.interface?.form
6318
- }
6319
- }
6320
- ) : null,
6321
- /* @__PURE__ */ jsx(
6322
- Modal,
6323
- {
6324
- opened: resourceDefinitionQuery.isLoading && !!selectedStep,
6325
- onClose: () => setSelectedStepKey(null),
6326
- withCloseButton: false,
6327
- centered: true,
6328
- children: /* @__PURE__ */ jsx(Center, { p: "md", children: /* @__PURE__ */ jsx(Loader, {}) })
6329
- }
6330
- )
6331
- ] }) });
6332
- }
6333
- var PAGE_SIZE_DEFAULT3 = 20;
6334
- function LeadGenCompaniesPage() {
6335
- const [companySearch, setCompanySearch] = useState("");
6336
- const [segmentFilter, setSegmentFilter] = useState(null);
6337
- const [categoryFilter, setCategoryFilter] = useState(null);
6338
- const [statusFilter, setStatusFilter] = useState(null);
6339
- const [selectedCompany, setSelectedCompany] = useState(null);
6340
- const [showBatchDelete, setShowBatchDelete] = useState(false);
6341
- const { data: companies, isLoading: companiesLoading } = useCompanies({
6342
- search: companySearch || void 0,
6343
- segment: segmentFilter || void 0,
6344
- category: categoryFilter || void 0,
6345
- status: statusFilter === "active" || statusFilter === "invalid" ? statusFilter : void 0
6346
- });
6347
- const deleteCompaniesMutation = useDeleteCompanies();
6348
- const { sort, toggleSort } = useTableSort("company", "asc");
6349
- const sortAccessors2 = useMemo(
6350
- () => ({
6351
- company: (company) => company.name,
6352
- domain: (company) => company.domain || "",
6353
- segment: (company) => company.segment || "",
6354
- category: (company) => company.category || "",
6355
- employees: (company) => company.numEmployees ?? 0,
6356
- enrichment: (company) => getEnrichmentStatus(company.enrichmentData),
6357
- contacts: (company) => company.contactCount
6358
- }),
6359
- []
6360
- );
6361
- const sortedCompanies = useMemo(
6362
- () => sortData(companies ?? [], sort, sortAccessors2),
6363
- [companies, sort, sortAccessors2]
6364
- );
6365
- const pagination = usePaginationState(PAGE_SIZE_DEFAULT3, [companySearch, segmentFilter, categoryFilter, statusFilter], sortedCompanies.length);
6366
- const paginatedCompanies = useMemo(
6367
- () => sortedCompanies.slice(pagination.offset, pagination.offset + PAGE_SIZE_DEFAULT3),
6368
- [sortedCompanies, pagination.offset]
6369
- );
6370
- const selection = useTableSelection(paginatedCompanies, sortedCompanies);
6371
- function handleBatchDelete() {
6372
- deleteCompaniesMutation.mutate([...selection.selectedIds], {
6373
- onSuccess: () => {
6374
- selection.clear();
6375
- setShowBatchDelete(false);
6376
- }
6377
- });
6378
- }
6379
- return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsxs(PageContainer, { children: [
6380
- /* @__PURE__ */ jsx(Stack, { children: /* @__PURE__ */ jsx(PageTitleCaption, { title: "Companies", caption: "Company records and enrichment tracking" }) }),
6381
- /* @__PURE__ */ jsx(Paper, { children: /* @__PURE__ */ jsxs(Stack, { children: [
6382
- /* @__PURE__ */ jsxs(
6383
- FilterBar,
6384
- {
6385
- actions: /* @__PURE__ */ jsx(
6386
- TableSelectionToolbar,
6387
- {
6388
- selectedCount: selection.selectedCount,
6389
- onDelete: () => setShowBatchDelete(true),
6390
- isDeleting: deleteCompaniesMutation.isPending
6391
- }
6392
- ),
6393
- children: [
6394
- /* @__PURE__ */ jsx(
6395
- Select,
6396
- {
6397
- placeholder: "All Segments",
6398
- data: ["Technology", "Marketing", "Finance", "Healthcare"],
6399
- value: segmentFilter,
6400
- onChange: setSegmentFilter,
6401
- style: { minWidth: 180 },
6402
- size: "sm",
6403
- clearable: true
6404
- }
6405
- ),
6406
- /* @__PURE__ */ jsx(
6407
- Select,
6408
- {
6409
- placeholder: "All Categories",
6410
- data: ["SaaS", "Service Agency", "Software"],
6411
- value: categoryFilter,
6412
- onChange: setCategoryFilter,
6413
- style: { minWidth: 160 },
6414
- size: "sm",
6415
- clearable: true
6416
- }
6417
- ),
6418
- /* @__PURE__ */ jsx(
6419
- Select,
6420
- {
6421
- placeholder: "All Statuses",
6422
- data: [
6423
- { value: "active", label: "Active" },
6424
- { value: "invalid", label: "Invalid" }
6425
- ],
6426
- value: statusFilter,
6427
- onChange: setStatusFilter,
6428
- style: { minWidth: 160 },
6429
- size: "sm",
6430
- clearable: true
6431
- }
6432
- ),
6433
- /* @__PURE__ */ jsx(
6434
- TextInput,
6435
- {
6436
- placeholder: "Search by name or domain...",
6437
- leftSection: /* @__PURE__ */ jsx(IconSearch, { size: 16 }),
6438
- style: { minWidth: 250 },
6439
- size: "sm",
6440
- value: companySearch,
6441
- onChange: (e) => setCompanySearch(e.target.value)
6442
- }
6443
- )
6444
- ]
6445
- }
6446
- ),
6447
- 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: [
6448
- /* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
6449
- /* @__PURE__ */ jsx(Table.Th, { w: 40, children: /* @__PURE__ */ jsx(
6450
- Checkbox,
6451
- {
6452
- checked: selection.isPageAllSelected,
6453
- indeterminate: selection.isPagePartiallySelected,
6454
- onChange: selection.togglePage
6455
- }
6456
- ) }),
6457
- /* @__PURE__ */ jsx(SortableHeader, { column: "company", sort, onToggle: toggleSort, children: "Company" }),
6458
- /* @__PURE__ */ jsx(SortableHeader, { column: "domain", sort, onToggle: toggleSort, children: "Domain" }),
6459
- /* @__PURE__ */ jsx(SortableHeader, { column: "segment", sort, onToggle: toggleSort, children: "Segment" }),
6460
- /* @__PURE__ */ jsx(SortableHeader, { column: "category", sort, onToggle: toggleSort, children: "Category" }),
6461
- /* @__PURE__ */ jsx(SortableHeader, { column: "employees", sort, onToggle: toggleSort, children: "Employees" }),
6462
- /* @__PURE__ */ jsx(SortableHeader, { column: "enrichment", sort, onToggle: toggleSort, children: "Enrichment" }),
6463
- /* @__PURE__ */ jsx(SortableHeader, { column: "contacts", sort, onToggle: toggleSort, children: "Contacts" })
6464
- ] }) }),
6465
- /* @__PURE__ */ jsx(Table.Tbody, { children: paginatedCompanies.map((company) => {
6466
- const enrichStatus = getEnrichmentStatus(company.enrichmentData);
6467
- return /* @__PURE__ */ jsxs(
6468
- Table.Tr,
6469
- {
6470
- style: { cursor: "pointer" },
6471
- onClick: () => setSelectedCompany(company),
6472
- children: [
6473
- /* @__PURE__ */ jsx(
6474
- Table.Td,
6475
- {
6476
- onClick: (e) => {
6477
- e.stopPropagation();
6478
- selection.toggle(company.id);
6479
- },
6480
- children: /* @__PURE__ */ jsx(
6481
- Checkbox,
6482
- {
6483
- checked: selection.isSelected(company.id),
6484
- onChange: () => selection.toggle(company.id),
6485
- onClick: (e) => e.stopPropagation()
6486
- }
6487
- )
6488
- }
6489
- ),
6490
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { fw: 500, children: company.name }) }),
6491
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: company.domain || "-" }) }),
6492
- /* @__PURE__ */ jsx(Table.Td, { children: company.segment || "-" }),
6493
- /* @__PURE__ */ jsx(Table.Td, { children: company.category || "-" }),
6494
- /* @__PURE__ */ jsx(Table.Td, { children: company.numEmployees || "-" }),
6495
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Badge, { variant: "light", color: getEnrichmentColor(enrichStatus), size: "sm", children: enrichStatus }) }),
6496
- /* @__PURE__ */ jsx(Table.Td, { children: company.contactCount })
6497
- ]
6498
- },
6499
- company.id
6500
- );
6501
- }) })
6502
- ] }),
6503
- sortedCompanies.length > PAGE_SIZE_DEFAULT3 ? /* @__PURE__ */ jsx(Group, { justify: "center", children: /* @__PURE__ */ jsx(
6504
- Pagination,
6505
- {
6506
- value: pagination.page,
6507
- onChange: pagination.setPage,
6508
- total: pagination.totalPages(sortedCompanies.length),
6509
- size: "sm"
6510
- }
6511
- ) }) : null
6512
- ] }) }),
6513
- /* @__PURE__ */ jsx(CompanyDetailModal, { company: selectedCompany, onClose: () => setSelectedCompany(null) }),
6514
- /* @__PURE__ */ jsx(CustomModal, { opened: showBatchDelete, onClose: () => setShowBatchDelete(false), size: "sm", children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
6515
- /* @__PURE__ */ jsxs(Group, { gap: "sm", children: [
6516
- /* @__PURE__ */ jsx(IconAlertTriangle, { size: 20, color: "var(--mantine-color-red-6)" }),
6517
- /* @__PURE__ */ jsxs(Title, { order: 5, children: [
6518
- "Delete ",
6519
- selection.selectedCount,
6520
- " companies?"
6521
- ] })
6522
- ] }),
6523
- /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "This will permanently delete the selected companies and cannot be undone." }),
6524
- /* @__PURE__ */ jsxs(Group, { justify: "flex-end", gap: "sm", children: [
6525
- /* @__PURE__ */ jsx(Button, { variant: "default", size: "sm", onClick: () => setShowBatchDelete(false), children: "Cancel" }),
6526
- /* @__PURE__ */ jsx(Button, { color: "red", size: "sm", loading: deleteCompaniesMutation.isPending, onClick: handleBatchDelete, children: "Delete" })
6527
- ] })
6528
- ] }) })
6529
- ] }) });
6530
- }
6531
- var PAGE_SIZE_DEFAULT4 = 20;
6532
- function LeadGenContactsPage() {
6533
- const [contactSearch, setContactSearch] = useState("");
6534
- const [statusFilter, setStatusFilter] = useState(null);
6535
- const [listFilter, setListFilter] = useState(null);
6536
- const [selectedContact, setSelectedContact] = useState(null);
6537
- const [showBatchDelete, setShowBatchDelete] = useState(false);
6538
- const { data: lists } = useLists();
6539
- const { data: contacts, isLoading: contactsLoading } = useContacts({
6540
- search: contactSearch || void 0,
6541
- contactStatus: statusFilter === "active" || statusFilter === "invalid" ? statusFilter : void 0,
6542
- listId: listFilter || void 0
6543
- });
6544
- const deleteContactsMutation = useDeleteContacts();
6545
- const { sort, toggleSort } = useTableSort("name", "asc");
6546
- const sortAccessors2 = useMemo(
6547
- () => ({
6548
- name: (contact) => [contact.firstName, contact.lastName].filter(Boolean).join(" ") || contact.email,
6549
- email: (contact) => contact.email,
6550
- company: (contact) => contact.company?.name || "",
6551
- title: (contact) => contact.title || "",
6552
- status: (contact) => contact.status || "",
6553
- personalized: (contact) => contact.openingLine ? "yes" : "no"
6554
- }),
6555
- []
6556
- );
6557
- const sortedContacts = useMemo(
6558
- () => sortData(contacts ?? [], sort, sortAccessors2),
6559
- [contacts, sort, sortAccessors2]
6560
- );
6561
- const pagination = usePaginationState(PAGE_SIZE_DEFAULT4, [contactSearch, statusFilter, listFilter], sortedContacts.length);
6562
- const paginatedContacts = useMemo(
6563
- () => sortedContacts.slice(pagination.offset, pagination.offset + PAGE_SIZE_DEFAULT4),
6564
- [sortedContacts, pagination.offset]
6565
- );
6566
- const selection = useTableSelection(paginatedContacts, sortedContacts);
6567
- function handleBatchDelete() {
6568
- deleteContactsMutation.mutate([...selection.selectedIds], {
6569
- onSuccess: () => {
6570
- selection.clear();
6571
- setShowBatchDelete(false);
6572
- }
6573
- });
6574
- }
6575
- return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsxs(PageContainer, { children: [
6576
- /* @__PURE__ */ jsx(Stack, { children: /* @__PURE__ */ jsx(PageTitleCaption, { title: "Contacts", caption: "Contact records and outreach tracking" }) }),
6577
- /* @__PURE__ */ jsx(Paper, { children: /* @__PURE__ */ jsxs(Stack, { children: [
6578
- /* @__PURE__ */ jsxs(
6579
- FilterBar,
6580
- {
6581
- actions: /* @__PURE__ */ jsx(
6582
- TableSelectionToolbar,
6583
- {
6584
- selectedCount: selection.selectedCount,
6585
- onDelete: () => setShowBatchDelete(true),
6586
- isDeleting: deleteContactsMutation.isPending
6587
- }
6588
- ),
6589
- children: [
6590
- /* @__PURE__ */ jsx(
6591
- Select,
6592
- {
6593
- placeholder: "All Statuses",
6594
- data: [
6595
- { value: "active", label: "Active" },
6596
- { value: "invalid", label: "Invalid" }
6597
- ],
6598
- value: statusFilter,
6599
- onChange: setStatusFilter,
6600
- style: { minWidth: 160 },
6601
- size: "sm",
6602
- clearable: true
6603
- }
6604
- ),
6605
- /* @__PURE__ */ jsx(
6606
- Select,
6607
- {
6608
- placeholder: "All Lists",
6609
- data: (lists ?? []).map((list) => ({ value: list.id, label: list.name })),
6610
- value: listFilter,
6611
- onChange: setListFilter,
6612
- style: { minWidth: 200 },
6613
- size: "sm",
6614
- clearable: true
6615
- }
6616
- ),
6617
- /* @__PURE__ */ jsx(
6618
- TextInput,
6619
- {
6620
- placeholder: "Search by name or email...",
6621
- leftSection: /* @__PURE__ */ jsx(IconSearch, { size: 16 }),
6622
- style: { minWidth: 250 },
6623
- size: "sm",
6624
- value: contactSearch,
6625
- onChange: (e) => setContactSearch(e.target.value)
6626
- }
6627
- )
6628
- ]
6629
- }
6630
- ),
6631
- 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: [
6632
- /* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
6633
- /* @__PURE__ */ jsx(Table.Th, { w: 40, children: /* @__PURE__ */ jsx(
6634
- Checkbox,
6635
- {
6636
- checked: selection.isPageAllSelected,
6637
- indeterminate: selection.isPagePartiallySelected,
6638
- onChange: selection.togglePage
6639
- }
6640
- ) }),
6641
- /* @__PURE__ */ jsx(SortableHeader, { column: "name", sort, onToggle: toggleSort, children: "Name" }),
6642
- /* @__PURE__ */ jsx(SortableHeader, { column: "email", sort, onToggle: toggleSort, children: "Email" }),
6643
- /* @__PURE__ */ jsx(SortableHeader, { column: "company", sort, onToggle: toggleSort, children: "Company" }),
6644
- /* @__PURE__ */ jsx(SortableHeader, { column: "title", sort, onToggle: toggleSort, children: "Title" }),
6645
- /* @__PURE__ */ jsx(SortableHeader, { column: "status", sort, onToggle: toggleSort, children: "Status" }),
6646
- /* @__PURE__ */ jsx(SortableHeader, { column: "personalized", sort, onToggle: toggleSort, children: "Personalized" })
6647
- ] }) }),
6648
- /* @__PURE__ */ jsx(Table.Tbody, { children: paginatedContacts.map((contact) => {
6649
- const fullName = [contact.firstName, contact.lastName].filter(Boolean).join(" ") || contact.email;
6650
- const hasPersonalization = !!contact.openingLine;
6651
- return /* @__PURE__ */ jsxs(
6652
- Table.Tr,
6653
- {
6654
- style: { cursor: "pointer" },
6655
- onClick: () => setSelectedContact(contact),
6656
- children: [
6657
- /* @__PURE__ */ jsx(
6658
- Table.Td,
6659
- {
6660
- onClick: (e) => {
6661
- e.stopPropagation();
6662
- selection.toggle(contact.id);
6663
- },
6664
- children: /* @__PURE__ */ jsx(
6665
- Checkbox,
6666
- {
6667
- checked: selection.isSelected(contact.id),
6668
- onChange: () => selection.toggle(contact.id),
6669
- onClick: (e) => e.stopPropagation()
6670
- }
6671
- )
6672
- }
6673
- ),
6674
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { fw: 500, children: fullName }) }),
6675
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: contact.email }) }),
6676
- /* @__PURE__ */ jsx(Table.Td, { children: contact.company?.name || "-" }),
6677
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: contact.title || "-" }) }),
6678
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Badge, { variant: "light", color: getStatusColor3(contact.status), size: "sm", children: contact.status || "unknown" }) }),
6679
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Badge, { variant: "light", color: hasPersonalization ? "green" : "gray", size: "sm", children: hasPersonalization ? "Yes" : "No" }) })
6680
- ]
6681
- },
6682
- contact.id
6683
- );
6684
- }) })
6685
- ] }),
6686
- sortedContacts.length > PAGE_SIZE_DEFAULT4 ? /* @__PURE__ */ jsx(Group, { justify: "center", children: /* @__PURE__ */ jsx(
6687
- Pagination,
6688
- {
6689
- value: pagination.page,
6690
- onChange: pagination.setPage,
6691
- total: pagination.totalPages(sortedContacts.length),
6692
- size: "sm"
6693
- }
6694
- ) }) : null
6695
- ] }) }),
6696
- /* @__PURE__ */ jsx(ContactDetailModal, { contact: selectedContact, onClose: () => setSelectedContact(null) }),
6697
- /* @__PURE__ */ jsx(CustomModal, { opened: showBatchDelete, onClose: () => setShowBatchDelete(false), size: "sm", children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
6698
- /* @__PURE__ */ jsxs(Group, { gap: "sm", children: [
6699
- /* @__PURE__ */ jsx(IconAlertTriangle, { size: 20, color: "var(--mantine-color-red-6)" }),
6700
- /* @__PURE__ */ jsxs(Title, { order: 5, children: [
6701
- "Delete ",
6702
- selection.selectedCount,
6703
- " contacts?"
6704
- ] })
6705
- ] }),
6706
- /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "This will permanently delete the selected contacts and cannot be undone." }),
6707
- /* @__PURE__ */ jsxs(Group, { justify: "flex-end", gap: "sm", children: [
6708
- /* @__PURE__ */ jsx(Button, { variant: "default", size: "sm", onClick: () => setShowBatchDelete(false), children: "Cancel" }),
6709
- /* @__PURE__ */ jsx(Button, { color: "red", size: "sm", loading: deleteContactsMutation.isPending, onClick: handleBatchDelete, children: "Delete" })
6710
- ] })
6711
- ] }) })
6712
- ] }) });
6713
- }
6714
- var SEOSidebarTop = () => {
6715
- return /* @__PURE__ */ jsx(SubshellSidebarSection, { icon: IconSearch, label: "SEO" });
6716
- };
6717
- var NAV_ITEMS = [
6718
- { label: "SEO Pages", to: "/seo", icon: IconChartBar, exact: true },
6719
- { label: "Metrics", to: "/seo/metrics", icon: IconTrendingUp, exact: false }
6720
- ];
6721
- var SEOSidebarMiddle = () => {
6722
- const { currentPath, navigate } = useRouterContext();
6723
- return /* @__PURE__ */ jsx(Stack, { gap: "xs", p: "sm", style: { flex: 1, overflowY: "auto" }, children: NAV_ITEMS.map((item) => {
6724
- const isActive = item.exact ? currentPath === item.to || currentPath === `${item.to}/` : currentPath.startsWith(item.to);
6725
- return /* @__PURE__ */ jsx(
6726
- SubshellNavItem,
6727
- {
6728
- icon: item.icon,
6729
- label: item.label,
6730
- isActive,
6731
- onClick: () => navigate(item.to)
6732
- },
6733
- item.to
6734
- );
6735
- }) });
6736
- };
6737
- var SEOSidebar = () => {
6738
- return /* @__PURE__ */ jsxs(Stack, { gap: 0, style: { height: "100%", display: "flex", flexDirection: "column" }, children: [
6739
- /* @__PURE__ */ jsx(SEOSidebarTop, {}),
6740
- /* @__PURE__ */ jsx(SEOSidebarMiddle, {})
6741
- ] });
6742
- };
6743
-
6744
- // src/features/seo/manifest.ts
6745
- var seoManifest = {
6746
- key: "seo",
6747
- label: "SEO",
6748
- sidebar: SEOSidebar,
6749
- subshellRoutes: ["/seo"]
6750
- };
6751
- function ringColor(completed, total) {
6752
- if (total === 0) return "gray";
6753
- const pct = completed / total;
6754
- if (pct > 0.75) return "green";
6755
- if (pct > 0.5) return "yellow";
6756
- return "gray";
6757
- }
6758
- function ringValue(completed, total) {
6759
- if (total === 0) return 0;
6760
- return Math.round(completed / total * 100);
6761
- }
6762
- function formatDate4(iso) {
6763
- return new Date(iso).toLocaleDateString();
6764
- }
6765
- function daysRelative(targetEndDate) {
6766
- const now = /* @__PURE__ */ new Date();
6767
- const end = new Date(targetEndDate);
6768
- const diffMs = end.getTime() - now.getTime();
6769
- const diffDays = Math.ceil(diffMs / 864e5);
6770
- if (diffDays >= 0) {
6771
- return { label: `${diffDays} day${diffDays === 1 ? "" : "s"} remaining`, overdue: false };
6772
- }
6773
- return { label: `${Math.abs(diffDays)} day${Math.abs(diffDays) === 1 ? "" : "s"} overdue`, overdue: true };
6774
- }
6775
- function HealthStatusCard({
6776
- status,
6777
- milestoneCount,
6778
- completedMilestones,
6779
- taskCount,
6780
- completedTasks,
6781
- startDate,
6782
- targetEndDate
6783
- }) {
6784
- const milestoneRingValue = ringValue(completedMilestones, milestoneCount);
6785
- const taskRingValue = ringValue(completedTasks, taskCount);
6786
- const timeInfo = targetEndDate ? daysRelative(targetEndDate) : null;
6787
- return /* @__PURE__ */ jsxs(SimpleGrid, { cols: 4, children: [
6788
- /* @__PURE__ */ jsx(Card, { withBorder: true, radius: "md", p: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: 8, children: [
6789
- /* @__PURE__ */ jsxs(Group, { gap: 6, children: [
6790
- /* @__PURE__ */ jsx(ThemeIcon, { size: 20, variant: "light", color: "gray", children: /* @__PURE__ */ jsx(IconHeartbeat, { size: 14 }) }),
6791
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", fw: 500, children: "Status" })
6792
- ] }),
6793
- /* @__PURE__ */ jsx(StatusBadge, { status, size: "md" })
6794
- ] }) }),
6795
- /* @__PURE__ */ jsx(Card, { withBorder: true, radius: "md", p: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: 8, children: [
6796
- /* @__PURE__ */ jsxs(Group, { gap: 6, children: [
6797
- /* @__PURE__ */ jsx(ThemeIcon, { size: 20, variant: "light", color: "gray", children: /* @__PURE__ */ jsx(IconFlag, { size: 14 }) }),
6798
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", fw: 500, children: "Milestones" })
6799
- ] }),
6800
- /* @__PURE__ */ jsxs(Group, { gap: 10, align: "center", children: [
6801
- /* @__PURE__ */ jsx(
6802
- RingProgress,
6803
- {
6804
- size: 48,
6805
- thickness: 5,
6806
- sections: [{ value: milestoneRingValue, color: ringColor(completedMilestones, milestoneCount) }]
6807
- }
6808
- ),
6809
- /* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
6810
- /* @__PURE__ */ jsxs(Text, { size: "sm", fw: 600, children: [
6811
- completedMilestones,
6812
- "/",
6813
- milestoneCount
6814
- ] }),
6815
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "complete" })
6816
- ] })
6817
- ] })
6818
- ] }) }),
6819
- /* @__PURE__ */ jsx(Card, { withBorder: true, radius: "md", p: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: 8, children: [
6820
- /* @__PURE__ */ jsxs(Group, { gap: 6, children: [
6821
- /* @__PURE__ */ jsx(ThemeIcon, { size: 20, variant: "light", color: "gray", children: /* @__PURE__ */ jsx(IconFileText, { size: 14 }) }),
6822
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", fw: 500, children: "Tasks" })
6823
- ] }),
6824
- /* @__PURE__ */ jsxs(Group, { gap: 10, align: "center", children: [
6825
- /* @__PURE__ */ jsx(
6826
- RingProgress,
6827
- {
6828
- size: 48,
6829
- thickness: 5,
6830
- sections: [{ value: taskRingValue, color: ringColor(completedTasks, taskCount) }]
6831
- }
6832
- ),
6833
- /* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
6834
- /* @__PURE__ */ jsxs(Text, { size: "sm", fw: 600, children: [
6835
- completedTasks,
6836
- "/",
6837
- taskCount
6838
- ] }),
6839
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "approved" })
6840
- ] })
6841
- ] })
6842
- ] }) }),
6843
- /* @__PURE__ */ jsx(Card, { withBorder: true, radius: "md", p: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: 8, children: [
6844
- /* @__PURE__ */ jsxs(Group, { gap: 6, children: [
6845
- /* @__PURE__ */ jsx(ThemeIcon, { size: 20, variant: "light", color: "gray", children: /* @__PURE__ */ jsx(IconCalendar, { size: 14 }) }),
6846
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", fw: 500, children: "Timeline" })
6847
- ] }),
6848
- /* @__PURE__ */ jsx(Stack, { gap: 2, children: startDate && targetEndDate ? /* @__PURE__ */ jsxs(Fragment, { children: [
6849
- /* @__PURE__ */ jsxs(Text, { size: "sm", fw: 500, children: [
6850
- formatDate4(startDate),
6851
- " \u2192 ",
6852
- formatDate4(targetEndDate)
6853
- ] }),
6854
- timeInfo && /* @__PURE__ */ jsx(Text, { size: "xs", c: timeInfo.overdue ? "red" : "dimmed", children: timeInfo.label })
6855
- ] }) : startDate ? /* @__PURE__ */ jsxs(Text, { size: "sm", fw: 500, children: [
6856
- "Started ",
6857
- formatDate4(startDate)
6858
- ] }) : targetEndDate ? /* @__PURE__ */ jsxs(Text, { size: "sm", fw: 500, children: [
6859
- "Due ",
6860
- formatDate4(targetEndDate)
6861
- ] }) : /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "No dates set" }) })
6862
- ] }) })
6863
- ] });
6864
- }
6865
-
6866
- // src/features/delivery/_shared.ts
6867
- var projectStatusColors = {
6868
- active: "blue",
6869
- on_track: "green",
6870
- at_risk: "yellow",
6871
- blocked: "red",
6872
- completed: "teal",
6873
- paused: "gray"
6874
- };
6875
- var milestoneStatusColors = {
6876
- upcoming: "gray",
6877
- in_progress: "blue",
6878
- completed: "green",
6879
- overdue: "red",
6880
- blocked: "orange"
6881
- };
6882
- var taskStatusColors = {
6883
- pending: "gray",
6884
- planned: "gray",
6885
- in_progress: "blue",
6886
- submitted: "yellow",
6887
- approved: "green",
6888
- rejected: "red",
6889
- revision_requested: "orange"
6890
- };
6891
- var taskTypeColors = {
6892
- documentation: "blue",
6893
- code: "violet",
6894
- report: "cyan",
6895
- design: "pink",
6896
- other: "gray"
6897
- };
6898
- var noteTypeColors = {
6899
- call_note: "blue",
6900
- status_update: "green",
6901
- issue: "yellow",
6902
- blocker: "red"
6903
- };
6904
- function formatStatusLabel(status) {
6905
- return status.split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
6906
- }
6907
- function calculateProgress(completed, total) {
6908
- if (total === 0) return 0;
6909
- return Math.round(completed / total * 100);
6910
- }
6911
- var MILESTONE_ICONS = {
6912
- upcoming: IconClock,
6913
- in_progress: IconFlag,
6914
- completed: IconCircleCheck,
6915
- overdue: IconAlertTriangle,
6916
- blocked: IconLock
6917
- };
6918
- function MilestoneTimeline({ milestones, tasks }) {
6919
- const sorted = [...milestones].sort((a, b) => a.sequence - b.sequence);
6920
- const defaultExpanded = new Set(sorted.filter((m) => m.status === "in_progress").map((m) => m.id));
6921
- const [expanded, setExpanded] = useState(defaultExpanded);
6922
- if (sorted.length === 0) {
6923
- return /* @__PURE__ */ jsx(EmptyState, { icon: IconInbox, title: "No milestones yet" });
6924
- }
6925
- function toggleMilestone(id) {
6926
- setExpanded((prev) => {
6927
- const next = new Set(prev);
6928
- if (next.has(id)) {
6929
- next.delete(id);
6930
- } else {
6931
- next.add(id);
6932
- }
6933
- return next;
6934
- });
6935
- }
6936
- const activeIndex = sorted.reduce((last, m, i) => m.status !== "upcoming" ? i : last, -1);
6937
- return /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsx(Timeline, { active: activeIndex, bulletSize: 28, lineWidth: 2, color: "var(--color-primary)", children: sorted.map((milestone) => {
6938
- const Icon = MILESTONE_ICONS[milestone.status] || IconFlag;
6939
- const color = milestoneStatusColors[milestone.status] || "gray";
6940
- const isExpanded = expanded.has(milestone.id);
6941
- const milestoneTasks = tasks.filter((t) => t.milestone_id === milestone.id);
6942
- const ChevronIcon = isExpanded ? IconChevronDown : IconChevronRight;
6943
- return /* @__PURE__ */ jsx(
6944
- Timeline.Item,
6945
- {
6946
- bullet: /* @__PURE__ */ jsx(ThemeIcon, { size: 28, variant: "filled", color, radius: "xl", children: /* @__PURE__ */ jsx(Icon, { size: 14 }) }),
6947
- children: /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
6948
- /* @__PURE__ */ jsxs(
6949
- Group,
6950
- {
6951
- gap: 8,
6952
- style: { cursor: "pointer", userSelect: "none" },
6953
- onClick: () => toggleMilestone(milestone.id),
6954
- children: [
6955
- /* @__PURE__ */ jsx(Text, { size: "sm", fw: 700, children: milestone.name }),
6956
- /* @__PURE__ */ jsx(Badge, { size: "sm", variant: "light", color, children: formatStatusLabel(milestone.status) }),
6957
- /* @__PURE__ */ jsx(ThemeIcon, { size: 16, variant: "transparent", color: "dimmed", children: /* @__PURE__ */ jsx(ChevronIcon, { size: 14 }) })
6958
- ]
6959
- }
6960
- ),
6961
- milestone.due_date && /* @__PURE__ */ jsxs(Text, { size: "xs", c: "dimmed", children: [
6962
- "Due ",
6963
- new Date(milestone.due_date).toLocaleDateString()
6964
- ] }),
6965
- /* @__PURE__ */ jsx(Collapse, { in: isExpanded, children: /* @__PURE__ */ jsx(Stack, { gap: 6, mt: 6, children: milestoneTasks.length === 0 ? /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "No tasks" }) : milestoneTasks.map((task) => /* @__PURE__ */ jsxs(Card, { withBorder: true, p: "xs", children: [
6966
- /* @__PURE__ */ jsxs(Group, { gap: 6, wrap: "nowrap", children: [
6967
- /* @__PURE__ */ jsx(Text, { size: "xs", fw: 500, style: { flex: 1, minWidth: 0 }, children: task.name }),
6968
- /* @__PURE__ */ jsx(Badge, { size: "xs", variant: "light", color: taskTypeColors[task.type] || "gray", children: formatStatusLabel(task.type) }),
6969
- /* @__PURE__ */ jsx(Badge, { size: "xs", variant: "light", color: taskStatusColors[task.status] || "gray", children: formatStatusLabel(task.status) })
6970
- ] }),
6971
- task.due_date && /* @__PURE__ */ jsxs(Text, { size: "xs", c: "dimmed", mt: 4, children: [
6972
- "Due ",
6973
- new Date(task.due_date).toLocaleDateString()
6974
- ] })
6975
- ] }, task.id)) }) })
6976
- ] })
6977
- },
6978
- milestone.id
6979
- );
6980
- }) }) });
6981
- }
6982
- function TaskCard({ task }) {
6983
- return /* @__PURE__ */ jsx(Card, { withBorder: true, radius: "sm", p: "sm", children: /* @__PURE__ */ jsxs(Stack, { gap: 6, children: [
6984
- /* @__PURE__ */ jsxs(Group, { justify: "space-between", wrap: "nowrap", children: [
6985
- /* @__PURE__ */ jsxs(Group, { gap: 6, wrap: "nowrap", style: { minWidth: 0 }, children: [
6986
- /* @__PURE__ */ jsx(Text, { fw: 500, size: "sm", style: { flexShrink: 0 }, children: task.name }),
6987
- /* @__PURE__ */ jsx(Badge, { size: "xs", variant: "light", color: taskTypeColors[task.type] || "gray", children: formatStatusLabel(task.type) }),
6988
- /* @__PURE__ */ jsx(StatusBadge, { status: task.status, size: "xs" })
6989
- ] }),
6990
- task.file_url && /* @__PURE__ */ jsx(
6991
- ActionIcon,
6992
- {
6993
- component: "a",
6994
- href: task.file_url,
6995
- target: "_blank",
6996
- rel: "noopener noreferrer",
6997
- variant: "light",
6998
- size: "sm",
6999
- "aria-label": "Download file",
7000
- children: /* @__PURE__ */ jsx(IconDownload, { size: 14 })
7001
- }
7002
- )
7003
- ] }),
7004
- task.description && /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: task.description }),
7005
- task.due_date && /* @__PURE__ */ jsxs(Text, { size: "xs", c: "dimmed", children: [
7006
- "Due ",
7007
- new Date(task.due_date).toLocaleDateString()
7008
- ] })
7009
- ] }) });
7010
- }
7011
- var ProjectsSidebarTop = () => {
7012
- return /* @__PURE__ */ jsx(SubshellSidebarSection, { icon: IconBriefcase, label: "Projects" });
7013
- };
7014
- var PROJECT_ITEMS = [{ label: "Projects", to: "/projects", icon: IconBriefcase, exact: true }];
7015
- var WORK_ITEMS = [
7016
- { label: "Tasks", to: "/projects/tasks", icon: IconChecklist, exact: false },
7017
- { label: "Milestones", to: "/projects/milestones", icon: IconFlag, exact: false }
7018
- ];
7019
- var COMMUNICATION_ITEMS = [{ label: "Notes", to: "/projects/notes", icon: IconNotes, exact: false }];
7020
- var ProjectsSidebarMiddle = ({ currentPath, onNavigate } = {}) => {
7021
- const { currentPath: routerCurrentPath, navigate } = useRouterContext();
7022
- const resolvedCurrentPath = currentPath ?? routerCurrentPath;
7023
- const resolvedNavigate = onNavigate ?? navigate;
7024
- const renderItems = (items) => items.map((item) => {
7025
- const isActive = item.exact ? resolvedCurrentPath === item.to || resolvedCurrentPath === `${item.to}/` : resolvedCurrentPath.startsWith(item.to);
7026
- return /* @__PURE__ */ jsx(
7027
- SubshellNavItem,
7028
- {
7029
- icon: item.icon,
7030
- label: item.label,
7031
- isActive,
7032
- onClick: () => resolvedNavigate(item.to)
7033
- },
7034
- item.to
7035
- );
7036
- });
7037
- return /* @__PURE__ */ jsxs(Stack, { gap: 0, style: { flex: 1, overflowY: "auto" }, children: [
7038
- /* @__PURE__ */ jsx(Stack, { gap: 0, p: "sm", children: renderItems(PROJECT_ITEMS) }),
7039
- /* @__PURE__ */ jsxs(Fragment, { children: [
7040
- /* @__PURE__ */ jsx(SubshellSidebarSection, { icon: IconListCheck, label: "Work", withTopBorder: true }),
7041
- /* @__PURE__ */ jsx(Stack, { gap: 0, p: "sm", children: renderItems(WORK_ITEMS) })
7042
- ] }),
7043
- /* @__PURE__ */ jsxs(Fragment, { children: [
7044
- /* @__PURE__ */ jsx(SubshellSidebarSection, { icon: IconMessageCircle, label: "Communication", withTopBorder: true }),
7045
- /* @__PURE__ */ jsx(Stack, { gap: 0, p: "sm", children: renderItems(COMMUNICATION_ITEMS) })
7046
- ] })
7047
- ] });
7048
- };
7049
- var ProjectsSidebar = () => {
7050
- return /* @__PURE__ */ jsxs(Stack, { gap: 0, style: { height: "100%", display: "flex", flexDirection: "column" }, children: [
7051
- /* @__PURE__ */ jsx(ProjectsSidebarTop, {}),
7052
- /* @__PURE__ */ jsx(ProjectsSidebarMiddle, {})
7053
- ] });
7054
- };
7055
- var deliveryManifest = {
7056
- key: "delivery",
7057
- label: "Projects",
7058
- navEntry: {
7059
- label: "Projects",
7060
- icon: IconBriefcase,
7061
- link: "/projects",
7062
- featureKey: "delivery"
7063
- },
7064
- sidebar: ProjectsSidebar,
7065
- subshellRoutes: ["/projects"]
7066
- };
7067
- var STATUS_OPTIONS = [
7068
- { value: "active", label: "Active" },
7069
- { value: "on_track", label: "On Track" },
7070
- { value: "at_risk", label: "At Risk" },
7071
- { value: "blocked", label: "Blocked" },
7072
- { value: "completed", label: "Completed" },
7073
- { value: "paused", label: "Paused" }
7074
- ];
7075
- function ProjectsListPage({ onProjectClick } = {}) {
7076
- const [statusFilter, setStatusFilter] = useState(null);
7077
- const [searchQuery, setSearchQuery] = useState("");
7078
- const [showBatchDelete, setShowBatchDelete] = useState(false);
7079
- const { data: projects, isLoading, error } = useProjects();
7080
- const deleteProject = useDeleteProject();
7081
- const { sort, toggleSort } = useTableSort("updated");
7082
- const filteredProjects = useMemo(() => {
7083
- const source = projects ?? [];
7084
- const query = searchQuery.trim().toLowerCase();
7085
- return source.filter((project) => {
7086
- const matchesStatus = !statusFilter || project.status === statusFilter;
7087
- const matchesSearch = !query || project.name.toLowerCase().includes(query);
7088
- return matchesStatus && matchesSearch;
7089
- });
7090
- }, [projects, searchQuery, statusFilter]);
7091
- const sortAccessors2 = useMemo(
7092
- () => ({
7093
- name: (p) => p.name || "",
7094
- status: (p) => p.status || "",
7095
- updated: (p) => p.updated_at || ""
7096
- }),
7097
- []
7098
- );
7099
- const sortedProjects = useMemo(() => sortData(filteredProjects, sort, sortAccessors2), [filteredProjects, sort, sortAccessors2]);
7100
- const pagination = usePaginationState(PAGE_SIZE_DEFAULT, [statusFilter, searchQuery], sortedProjects.length);
7101
- const paginatedProjects = useMemo(
7102
- () => sortedProjects.slice(pagination.offset, pagination.offset + PAGE_SIZE_DEFAULT),
7103
- [sortedProjects, pagination.offset]
7104
- );
7105
- const selection = useTableSelection(paginatedProjects, sortedProjects);
7106
- const handleDeleteSelected = async () => {
7107
- await Promise.all([...selection.selectedIds].map((projectId) => deleteProject.mutateAsync(projectId)));
7108
- setShowBatchDelete(false);
7109
- selection.clear();
7110
- };
7111
- return /* @__PURE__ */ jsxs(SubshellContentContainer, { children: [
7112
- /* @__PURE__ */ jsxs(PageContainer, { children: [
7113
- /* @__PURE__ */ jsx(Stack, { children: /* @__PURE__ */ jsx(PageTitleCaption, { title: "Projects", caption: "Client delivery tracking and milestone management" }) }),
7114
- /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { children: [
7115
- /* @__PURE__ */ jsxs(
7116
- FilterBar,
7117
- {
7118
- actions: /* @__PURE__ */ jsx(
7119
- TableSelectionToolbar,
7120
- {
7121
- selectedCount: selection.selectedCount,
7122
- onDelete: () => setShowBatchDelete(true),
7123
- isDeleting: deleteProject.isPending
7124
- }
7125
- ),
7126
- children: [
7127
- /* @__PURE__ */ jsx(
7128
- Select,
7129
- {
7130
- placeholder: "All Statuses",
7131
- data: STATUS_OPTIONS,
7132
- style: { minWidth: 180 },
7133
- size: "sm",
7134
- clearable: true,
7135
- value: statusFilter,
7136
- onChange: setStatusFilter
7137
- }
7138
- ),
7139
- /* @__PURE__ */ jsx(
7140
- TextInput,
7141
- {
7142
- placeholder: "Search by name...",
7143
- leftSection: /* @__PURE__ */ jsx(IconSearch, { size: 16 }),
7144
- style: { minWidth: 250 },
7145
- size: "sm",
7146
- value: searchQuery,
7147
- onChange: (e) => setSearchQuery(e.currentTarget.value)
7148
- }
7149
- )
7150
- ]
7151
- }
7152
- ),
7153
- isLoading ? /* @__PURE__ */ jsx(Center, { p: "xl", children: /* @__PURE__ */ jsx(Loader, {}) }) : error ? /* @__PURE__ */ jsx(CenteredErrorState, { error, title: "Failed to load projects" }) : !sortedProjects.length ? /* @__PURE__ */ jsx(EmptyState, { icon: IconBriefcase, title: "No projects found" }) : /* @__PURE__ */ jsxs(Table, { children: [
7154
- /* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
7155
- /* @__PURE__ */ jsx(Table.Th, { w: 40, children: /* @__PURE__ */ jsx(
7156
- Checkbox,
7157
- {
7158
- checked: selection.isPageAllSelected,
7159
- indeterminate: selection.isPagePartiallySelected,
7160
- onChange: selection.togglePage
7161
- }
7162
- ) }),
7163
- /* @__PURE__ */ jsx(SortableHeader, { column: "name", sort, onToggle: toggleSort, children: "Name" }),
7164
- /* @__PURE__ */ jsx(SortableHeader, { column: "status", sort, onToggle: toggleSort, children: "Status" }),
7165
- /* @__PURE__ */ jsx(Table.Th, { children: "Milestones" }),
7166
- /* @__PURE__ */ jsx(Table.Th, { children: "Tasks" }),
7167
- /* @__PURE__ */ jsx(SortableHeader, { column: "updated", sort, onToggle: toggleSort, children: "Updated" })
7168
- ] }) }),
7169
- /* @__PURE__ */ jsx(Table.Tbody, { children: paginatedProjects.map((project) => {
7170
- const completedMilestones = project.completedMilestones ?? 0;
7171
- const milestoneCount = project.milestoneCount ?? 0;
7172
- const completedTasks = project.completedTasks ?? 0;
7173
- const taskCount = project.taskCount ?? 0;
7174
- const rowProps = onProjectClick ? {
7175
- style: { cursor: "pointer" },
7176
- onClick: () => onProjectClick(project.id)
7177
- } : void 0;
7178
- return /* @__PURE__ */ jsxs(Table.Tr, { ...rowProps, children: [
7179
- /* @__PURE__ */ jsx(Table.Td, { onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx(
7180
- Checkbox,
7181
- {
7182
- checked: selection.isSelected(project.id),
7183
- onChange: () => selection.toggle(project.id)
7184
- }
7185
- ) }),
7186
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { fw: 500, children: project.name }) }),
7187
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Badge, { variant: "light", color: projectStatusColors[project.status || ""] || "gray", size: "sm", children: formatStatusLabel(project.status || "unknown") }) }),
7188
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsxs(Text, { size: "sm", children: [
7189
- completedMilestones,
7190
- "/",
7191
- milestoneCount
7192
- ] }) }),
7193
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsxs(Text, { size: "sm", children: [
7194
- completedTasks,
7195
- "/",
7196
- taskCount
7197
- ] }) }),
7198
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: formatTimeAgo(project.updated_at) }) })
7199
- ] }, project.id);
7200
- }) })
7201
- ] }),
7202
- sortedProjects.length > PAGE_SIZE_DEFAULT && /* @__PURE__ */ jsx(Group, { justify: "center", children: /* @__PURE__ */ jsx(
7203
- Pagination,
7204
- {
7205
- value: pagination.page,
7206
- onChange: pagination.setPage,
7207
- total: pagination.totalPages(sortedProjects.length),
7208
- size: "sm"
7209
- }
7210
- ) })
7211
- ] }) })
7212
- ] }),
7213
- /* @__PURE__ */ jsx(
7214
- CustomModal,
7215
- {
7216
- opened: showBatchDelete,
7217
- onClose: () => !deleteProject.isPending && setShowBatchDelete(false),
7218
- size: "sm",
7219
- loading: deleteProject.isPending,
7220
- children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
7221
- /* @__PURE__ */ jsxs(Group, { gap: "sm", children: [
7222
- /* @__PURE__ */ jsx(IconAlertTriangle, { size: 24, color: "var(--color-error)" }),
7223
- /* @__PURE__ */ jsxs(Title, { order: 4, children: [
7224
- "Delete ",
7225
- selection.selectedCount,
7226
- " Projects"
7227
- ] })
7228
- ] }),
7229
- /* @__PURE__ */ jsxs(Text, { size: "sm", children: [
7230
- "Are you sure you want to delete",
7231
- " ",
7232
- /* @__PURE__ */ jsx(Text, { span: true, fw: 600, children: selection.selectedCount }),
7233
- " ",
7234
- "selected projects?"
7235
- ] }),
7236
- /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "This action cannot be undone." }),
7237
- /* @__PURE__ */ jsxs(Group, { justify: "flex-end", mt: "md", children: [
7238
- /* @__PURE__ */ jsx(Button, { variant: "light", onClick: () => setShowBatchDelete(false), disabled: deleteProject.isPending, children: "Cancel" }),
7239
- /* @__PURE__ */ jsx(Button, { color: "red", loading: deleteProject.isPending, onClick: () => void handleDeleteSelected(), children: "Delete" })
7240
- ] })
7241
- ] })
7242
- }
7243
- )
7244
- ] });
7245
- }
7246
- var noteTypeOptions = [
7247
- { value: "call_note", label: "Call Note" },
7248
- { value: "status_update", label: "Status Update" },
7249
- { value: "issue", label: "Issue" },
7250
- { value: "blocker", label: "Blocker" }
7251
- ];
7252
- function parseChecklist(milestone) {
7253
- const raw = milestone.checklist;
7254
- if (!Array.isArray(raw)) return [];
7255
- return raw;
7256
- }
7257
- function MilestoneChecklist({ milestone }) {
7258
- const items = parseChecklist(milestone);
7259
- const { mutate: updateMilestone } = useUpdateMilestone();
7260
- const [newItemLabel, setNewItemLabel] = useState("");
7261
- const [adding, setAdding] = useState(false);
7262
- function saveChecklist(updated) {
7263
- const allComplete = updated.length > 0 && updated.every((item) => item.completed);
7264
- const wasComplete = milestone.status === "completed";
7265
- const updates = {
7266
- checklist: updated
7267
- };
7268
- if (allComplete && !wasComplete) {
7269
- updates.status = "completed";
7270
- updates.completed_at = (/* @__PURE__ */ new Date()).toISOString();
7271
- } else if (!allComplete && wasComplete) {
7272
- updates.status = "in_progress";
7273
- updates.completed_at = null;
7274
- }
7275
- updateMilestone({ id: milestone.id, updates });
7276
- }
7277
- function toggleItem(itemId) {
7278
- const updated = items.map((item) => item.id === itemId ? { ...item, completed: !item.completed } : item);
7279
- saveChecklist(updated);
7280
- }
7281
- function addItem() {
7282
- const label = newItemLabel.trim();
7283
- if (!label) return;
7284
- const newItem = { id: crypto.randomUUID(), label, completed: false };
7285
- saveChecklist([...items, newItem]);
7286
- setNewItemLabel("");
7287
- }
7288
- function removeItem(itemId) {
7289
- saveChecklist(items.filter((item) => item.id !== itemId));
7290
- }
7291
- const completedCount = items.filter((item) => item.completed).length;
7292
- return /* @__PURE__ */ jsxs(Stack, { gap: "xs", mt: "sm", children: [
7293
- items.length > 0 && /* @__PURE__ */ jsxs(Text, { size: "xs", c: "dimmed", children: [
7294
- completedCount,
7295
- "/",
7296
- items.length,
7297
- " complete"
7298
- ] }),
7299
- items.map((item) => /* @__PURE__ */ jsxs(Group, { gap: "xs", wrap: "nowrap", children: [
7300
- /* @__PURE__ */ jsx(
7301
- Checkbox,
7302
- {
7303
- size: "xs",
7304
- checked: item.completed,
7305
- onChange: () => toggleItem(item.id),
7306
- label: /* @__PURE__ */ jsx(
7307
- Text,
7308
- {
7309
- size: "sm",
7310
- td: item.completed ? "line-through" : void 0,
7311
- c: item.completed ? "dimmed" : "var(--color-text-subtle)",
7312
- children: item.label
7313
- }
7314
- ),
7315
- styles: { input: { cursor: "pointer" }, label: { cursor: "pointer" } }
7316
- }
7317
- ),
7318
- /* @__PURE__ */ jsx(ActionIcon, { variant: "subtle", size: "xs", onClick: () => removeItem(item.id), style: { opacity: 0.4 }, children: /* @__PURE__ */ jsx(IconX, { size: 12 }) })
7319
- ] }, item.id)),
7320
- adding ? /* @__PURE__ */ jsx(
7321
- TextInput,
7322
- {
7323
- size: "xs",
7324
- placeholder: "Item label...",
7325
- value: newItemLabel,
7326
- onChange: (event) => setNewItemLabel(event.target.value),
7327
- onKeyDown: (event) => {
7328
- if (event.key === "Enter") {
7329
- addItem();
7330
- setAdding(false);
7331
- }
7332
- if (event.key === "Escape") {
7333
- setNewItemLabel("");
7334
- setAdding(false);
7335
- }
7336
- },
7337
- onBlur: () => {
7338
- if (newItemLabel.trim()) addItem();
7339
- setAdding(false);
7340
- },
7341
- autoFocus: true,
7342
- style: { maxWidth: 250 }
7343
- }
7344
- ) : /* @__PURE__ */ jsx(Group, { children: /* @__PURE__ */ jsx(Button, { variant: "subtle", size: "compact-xs", leftSection: /* @__PURE__ */ jsx(IconPlus, { size: 12 }), onClick: () => setAdding(true), children: "Add item" }) })
7345
- ] });
7346
- }
7347
- function AddNoteModal({ opened, onClose, projectId }) {
7348
- const [noteType, setNoteType] = useState("call_note");
7349
- const [content, setContent] = useState("");
7350
- const [summary, setSummary] = useState("");
7351
- const { mutate: createNote, isPending } = useCreateNote();
7352
- function handleSubmit() {
7353
- if (!content.trim() || !noteType) return;
7354
- createNote(
7355
- {
7356
- project_id: projectId,
7357
- type: noteType,
7358
- content: content.trim(),
7359
- summary: summary.trim() || null
7360
- },
7361
- {
7362
- onSuccess: () => {
7363
- setContent("");
7364
- setSummary("");
7365
- setNoteType("call_note");
7366
- onClose();
7367
- }
7368
- }
7369
- );
7370
- }
7371
- return /* @__PURE__ */ jsx(CustomModal, { opened, onClose, size: "md", loading: isPending, children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
7372
- /* @__PURE__ */ jsxs(Group, { justify: "space-between", align: "flex-start", children: [
7373
- /* @__PURE__ */ jsx(Title, { order: 3, children: "Add Note" }),
7374
- /* @__PURE__ */ jsx(ActionIcon, { variant: "subtle", onClick: onClose, disabled: isPending, children: /* @__PURE__ */ jsx(IconX, { size: 18 }) })
7375
- ] }),
7376
- /* @__PURE__ */ jsx(Select, { label: "Type", data: noteTypeOptions, value: noteType, onChange: setNoteType, required: true }),
7377
- /* @__PURE__ */ jsx(
7378
- TextInput,
7379
- {
7380
- label: "Summary",
7381
- placeholder: "Brief summary (optional)",
7382
- value: summary,
7383
- onChange: (event) => setSummary(event.target.value)
7384
- }
7385
- ),
7386
- /* @__PURE__ */ jsx(
7387
- Textarea,
7388
- {
7389
- label: "Content",
7390
- placeholder: "Note content...",
7391
- minRows: 4,
7392
- value: content,
7393
- onChange: (event) => setContent(event.target.value),
7394
- required: true
7395
- }
7396
- ),
7397
- /* @__PURE__ */ jsxs(Group, { justify: "flex-end", children: [
7398
- /* @__PURE__ */ jsx(Button, { variant: "light", onClick: onClose, disabled: isPending, children: "Cancel" }),
7399
- /* @__PURE__ */ jsx(Button, { onClick: handleSubmit, loading: isPending, disabled: !content.trim() || !noteType, children: "Add Note" })
7400
- ] })
7401
- ] }) });
7402
- }
7403
- function ProjectDetailPage({ projectId, onBack, backLabel = "Projects" }) {
7404
- const { data: project, isLoading, error } = useProject(projectId);
7405
- const { data: notes } = useProjectNotes({ projectId });
7406
- const { mutate: deleteProject, isPending: isDeleting } = useDeleteProject$1();
7407
- const [addNoteOpen, setAddNoteOpen] = useState(false);
7408
- const [deleteModalOpen, setDeleteModalOpen] = useState(false);
7409
- if (isLoading) {
7410
- return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsx(Center, { style: { flex: 1 }, children: /* @__PURE__ */ jsx(Loader, {}) }) });
7411
- }
7412
- if (error) {
7413
- return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsx(Center, { style: { flex: 1 }, children: /* @__PURE__ */ jsx(Alert, { color: "red", children: "Error loading project" }) }) });
7414
- }
7415
- if (!project) {
7416
- return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsx(Center, { style: { flex: 1 }, children: /* @__PURE__ */ jsx(Alert, { color: "yellow", children: "Project not found" }) }) });
7417
- }
7418
- const milestones = project.milestones ?? [];
7419
- const tasks = project.tasks ?? [];
7420
- const totalMilestones = milestones.length;
7421
- const completedMilestones = milestones.filter((milestone) => milestone.status === "completed").length;
7422
- const totalTasks = tasks.length;
7423
- const approvedTasks = tasks.filter((task) => task.status === "approved").length;
7424
- const milestoneProgress = calculateProgress(completedMilestones, totalMilestones);
7425
- const taskProgress = calculateProgress(approvedTasks, totalTasks);
7426
- const companyName = project.company?.name ?? null;
7427
- return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsx(PageContainer, { children: /* @__PURE__ */ jsxs(Stack, { children: [
7428
- /* @__PURE__ */ jsx(
7429
- PageTitleCaption,
7430
- {
7431
- title: project.name,
7432
- caption: [companyName, formatStatusLabel(project.status || "unknown")].filter(Boolean).join(" - "),
7433
- rightSection: /* @__PURE__ */ jsx(Group, { children: /* @__PURE__ */ jsx(Button, { variant: "light", size: "sm", leftSection: /* @__PURE__ */ jsx(IconArrowLeft, { size: 16 }), onClick: onBack, children: backLabel }) })
7434
- }
7435
- ),
7436
- /* @__PURE__ */ jsxs(SimpleGrid, { cols: 4, children: [
7437
- /* @__PURE__ */ jsx(
7438
- StatCard,
7439
- {
7440
- variant: "hero",
7441
- icon: IconHeartbeat,
7442
- value: formatStatusLabel(project.status || "unknown"),
7443
- label: "Status",
7444
- valueColor: `var(--mantine-color-${projectStatusColors[project.status || ""] || "gray"}-6)`
7445
- }
7446
- ),
7447
- /* @__PURE__ */ jsx(StatCard, { variant: "hero", icon: IconFlag, value: `${completedMilestones}/${totalMilestones}`, label: "Milestones", children: /* @__PURE__ */ jsxs(Text, { size: "xs", c: "dimmed", children: [
7448
- milestoneProgress,
7449
- "% complete"
7450
- ] }) }),
7451
- /* @__PURE__ */ jsx(StatCard, { variant: "hero", icon: IconChecklist, value: `${approvedTasks}/${totalTasks}`, label: "Tasks", children: /* @__PURE__ */ jsxs(Text, { size: "xs", c: "dimmed", children: [
7452
- taskProgress,
7453
- "% complete"
7454
- ] }) }),
7455
- /* @__PURE__ */ jsx(StatCard, { variant: "hero", icon: IconCalendar, value: project.start_date ? formatDate(project.start_date) : "-", label: "Timeline", children: project.target_end_date && /* @__PURE__ */ jsxs(Text, { size: "xs", c: "dimmed", children: [
7456
- "to ",
7457
- formatDate(project.target_end_date)
7458
- ] }) })
7459
- ] }),
7460
- /* @__PURE__ */ jsxs(
7461
- Paper,
7462
- {
7463
- p: "md",
7464
- style: {
7465
- border: "1px solid var(--color-border)",
7466
- borderRadius: "var(--mantine-radius-default)"
7467
- },
7468
- children: [
7469
- /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
7470
- /* @__PURE__ */ jsxs(Group, { justify: "space-between", wrap: "wrap", children: [
7471
- /* @__PURE__ */ jsxs(Group, { gap: "sm", children: [
7472
- /* @__PURE__ */ jsx(Title, { order: 3, children: "Project Details" }),
7473
- /* @__PURE__ */ jsx(Badge, { variant: "light", color: projectStatusColors[project.status || ""] || "gray", children: formatStatusLabel(project.status || "unknown") })
7474
- ] }),
7475
- /* @__PURE__ */ jsxs(Group, { gap: 4, children: [
7476
- /* @__PURE__ */ jsx(Text, { size: "sm", ff: "monospace", c: "var(--color-text-subtle)", children: project.id }),
7477
- /* @__PURE__ */ jsx(CopyButton, { value: project.id, children: ({ copied, copy }) => /* @__PURE__ */ jsx(Tooltip, { label: copied ? "Copied!" : "Copy ID", withArrow: true, children: /* @__PURE__ */ jsx(ActionIcon, { onClick: copy, variant: "subtle", size: "xs", color: copied ? "green" : "var(--color-text-subtle)", children: copied ? /* @__PURE__ */ jsx(IconCheck, { size: 14 }) : /* @__PURE__ */ jsx(IconCopy, { size: 14 }) }) }) })
7478
- ] })
7479
- ] }),
7480
- /* @__PURE__ */ jsxs(Group, { gap: "lg", children: [
7481
- project.start_date && /* @__PURE__ */ jsxs(Group, { gap: 4, children: [
7482
- /* @__PURE__ */ jsx(Text, { size: "sm", fw: 500, children: "Started:" }),
7483
- /* @__PURE__ */ jsx(Text, { size: "sm", children: formatDate(project.start_date) })
7484
- ] }),
7485
- project.target_end_date && /* @__PURE__ */ jsxs(Group, { gap: 4, children: [
7486
- /* @__PURE__ */ jsx(Text, { size: "sm", fw: 500, children: "Target:" }),
7487
- /* @__PURE__ */ jsx(Text, { size: "sm", children: formatDate(project.target_end_date) })
7488
- ] }),
7489
- project.contract_value != null && /* @__PURE__ */ jsxs(Group, { gap: 4, children: [
7490
- /* @__PURE__ */ jsx(Text, { size: "sm", fw: 500, children: "Contract:" }),
7491
- /* @__PURE__ */ jsxs(Text, { size: "sm", children: [
7492
- "$",
7493
- project.contract_value.toLocaleString(),
7494
- project.metadata && project.metadata.rate_type === "hourly" ? "/hr" : ""
7495
- ] })
7496
- ] }),
7497
- project.company && /* @__PURE__ */ jsxs(Group, { gap: 4, children: [
7498
- /* @__PURE__ */ jsx(Text, { size: "sm", fw: 500, children: "Company:" }),
7499
- /* @__PURE__ */ jsx(Text, { size: "sm", children: project.company.name })
7500
- ] })
7501
- ] })
7502
- ] }),
7503
- /* @__PURE__ */ jsx(Divider, { my: "md" }),
7504
- /* @__PURE__ */ jsxs(Tabs, { defaultValue: "status", children: [
7505
- /* @__PURE__ */ jsxs(Tabs.List, { children: [
7506
- /* @__PURE__ */ jsx(Tabs.Tab, { value: "status", children: "Status" }),
7507
- /* @__PURE__ */ jsx(Tabs.Tab, { value: "details", children: "Details" }),
7508
- /* @__PURE__ */ jsx(Tabs.Tab, { value: "notes", children: "Notes" }),
7509
- /* @__PURE__ */ jsx(Tabs.Tab, { value: "activity", children: "Activity" })
7510
- ] }),
7511
- /* @__PURE__ */ jsx(Tabs.Panel, { value: "status", pt: "md", children: milestones.length === 0 ? /* @__PURE__ */ jsx(Text, { c: "dimmed", size: "sm", children: "No milestones yet." }) : /* @__PURE__ */ jsx(
7512
- Timeline,
7513
- {
7514
- active: completedMilestones,
7515
- bulletSize: 20,
7516
- color: "var(--color-primary)",
7517
- styles: {
7518
- itemBullet: {
7519
- borderColor: "color-mix(in srgb, var(--color-primary) 40%, transparent)",
7520
- backgroundColor: "var(--color-surface)"
7521
- },
7522
- item: {
7523
- "--_item-border-color": "color-mix(in srgb, var(--color-primary) 25%, transparent)"
7524
- },
7525
- itemTitle: {
7526
- color: "var(--color-text)"
7527
- }
7528
- },
7529
- children: milestones.map((milestone) => {
7530
- const milestoneTasks = tasks.filter((task) => task.milestone_id === milestone.id);
7531
- return /* @__PURE__ */ jsxs(
7532
- Timeline.Item,
7533
- {
7534
- bullet: milestone.status === "completed" ? /* @__PURE__ */ jsx(
7535
- "div",
7536
- {
7537
- style: {
7538
- width: 12,
7539
- height: 12,
7540
- borderRadius: "50%",
7541
- backgroundColor: "var(--color-primary)",
7542
- boxShadow: "0 0 8px var(--color-primary), 0 0 16px color-mix(in srgb, var(--color-primary) 50%, transparent)"
7543
- }
7544
- }
7545
- ) : void 0,
7546
- title: /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
7547
- /* @__PURE__ */ jsx(Text, { fw: 500, children: milestone.name }),
7548
- /* @__PURE__ */ jsx(Badge, { variant: "light", color: milestoneStatusColors[milestone.status || ""] || "gray", size: "sm", children: formatStatusLabel(milestone.status || "unknown") }),
7549
- milestone.due_date && /* @__PURE__ */ jsxs(Text, { size: "xs", c: "dimmed", children: [
7550
- "Due ",
7551
- formatDate(milestone.due_date)
7552
- ] })
7553
- ] }),
7554
- children: [
7555
- milestone.description && /* @__PURE__ */ jsx(Text, { size: "sm", mt: "xs", children: milestone.description }),
7556
- /* @__PURE__ */ jsx(MilestoneChecklist, { milestone }),
7557
- milestoneTasks.length > 0 && /* @__PURE__ */ jsx(Stack, { gap: "xs", mt: "sm", children: milestoneTasks.map((task) => /* @__PURE__ */ jsx(Card, { withBorder: true, p: "xs", children: /* @__PURE__ */ jsxs(Group, { gap: "xs", justify: "space-between", children: [
7558
- /* @__PURE__ */ jsx(Text, { size: "sm", fw: 500, children: task.name }),
7559
- /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
7560
- /* @__PURE__ */ jsx(Badge, { variant: "light", color: taskTypeColors[task.type || ""] || "gray", size: "xs", children: formatStatusLabel(task.type || "other") }),
7561
- /* @__PURE__ */ jsx(Badge, { variant: "light", color: taskStatusColors[task.status || ""] || "gray", size: "xs", children: formatStatusLabel(task.status || "pending") })
7562
- ] })
7563
- ] }) }, task.id)) })
7564
- ]
7565
- },
7566
- milestone.id
7567
- );
7568
- })
7569
- }
7570
- ) }),
7571
- /* @__PURE__ */ jsx(Tabs.Panel, { value: "details", pt: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
7572
- /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
7573
- /* @__PURE__ */ jsx(Title, { order: 3, children: "Project Details" }),
7574
- project.description && /* @__PURE__ */ jsxs("div", { children: [
7575
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Description" }),
7576
- /* @__PURE__ */ jsx(Text, { size: "sm", children: project.description })
7577
- ] }),
7578
- project.contract_value != null && /* @__PURE__ */ jsxs("div", { children: [
7579
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Contract Value" }),
7580
- /* @__PURE__ */ jsxs(Text, { size: "sm", fw: 500, children: [
7581
- "$",
7582
- project.contract_value.toLocaleString()
7583
- ] })
7584
- ] }),
7585
- /* @__PURE__ */ jsxs(SimpleGrid, { cols: 3, children: [
7586
- /* @__PURE__ */ jsxs("div", { children: [
7587
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Start Date" }),
7588
- /* @__PURE__ */ jsx(Text, { size: "sm", children: formatDate(project.start_date) })
7589
- ] }),
7590
- /* @__PURE__ */ jsxs("div", { children: [
7591
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Target End Date" }),
7592
- /* @__PURE__ */ jsx(Text, { size: "sm", children: formatDate(project.target_end_date) })
7593
- ] }),
7594
- /* @__PURE__ */ jsxs("div", { children: [
7595
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Actual End Date" }),
7596
- /* @__PURE__ */ jsx(Text, { size: "sm", children: formatDate(project.actual_end_date) })
7597
- ] })
7598
- ] })
7599
- ] }) }),
7600
- project.company && /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
7601
- /* @__PURE__ */ jsx(Title, { order: 3, children: "Company" }),
7602
- /* @__PURE__ */ jsx(Text, { fw: 500, children: project.company.name }),
7603
- project.company.domain && /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: project.company.domain })
7604
- ] }) })
7605
- ] }) }),
7606
- /* @__PURE__ */ jsx(Tabs.Panel, { value: "notes", pt: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
7607
- /* @__PURE__ */ jsx(Group, { justify: "flex-end", children: /* @__PURE__ */ jsx(Button, { variant: "light", size: "sm", leftSection: /* @__PURE__ */ jsx(IconPlus, { size: 16 }), onClick: () => setAddNoteOpen(true), children: "Add Note" }) }),
7608
- !notes?.length ? /* @__PURE__ */ jsx(Text, { c: "dimmed", size: "sm", children: "No notes yet." }) : /* @__PURE__ */ jsx(Stack, { gap: "sm", children: notes.map((note) => /* @__PURE__ */ jsx(Card, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "xs", children: [
7609
- /* @__PURE__ */ jsxs(Group, { justify: "space-between", children: [
7610
- /* @__PURE__ */ jsx(Badge, { variant: "light", color: noteTypeColors[note.type || ""] || "gray", size: "sm", children: formatStatusLabel(note.type || "note") }),
7611
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: formatTimeAgo(note.occurred_at) })
7612
- ] }),
7613
- note.summary && /* @__PURE__ */ jsx(Text, { size: "sm", fw: 500, children: note.summary }),
7614
- /* @__PURE__ */ jsx(Text, { size: "sm", children: note.content })
7615
- ] }) }, note.id)) })
7616
- ] }) }),
7617
- /* @__PURE__ */ jsx(Tabs.Panel, { value: "activity", pt: "md", children: /* @__PURE__ */ jsx(Text, { c: "dimmed", size: "sm", children: "Activity feed coming soon." }) })
7618
- ] })
7619
- ]
7620
- }
7621
- ),
7622
- /* @__PURE__ */ jsx(AddNoteModal, { opened: addNoteOpen, onClose: () => setAddNoteOpen(false), projectId }),
7623
- /* @__PURE__ */ jsx(CustomModal, { opened: deleteModalOpen, onClose: () => setDeleteModalOpen(false), size: "sm", children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
7624
- /* @__PURE__ */ jsxs(Group, { justify: "space-between", align: "flex-start", children: [
7625
- /* @__PURE__ */ jsx(Title, { order: 4, children: "Delete project" }),
7626
- /* @__PURE__ */ jsx(ActionIcon, { variant: "subtle", onClick: () => setDeleteModalOpen(false), children: /* @__PURE__ */ jsx(IconX, { size: 18 }) })
7627
- ] }),
7628
- /* @__PURE__ */ jsxs(Text, { size: "sm", children: [
7629
- "Are you sure you want to delete ",
7630
- /* @__PURE__ */ jsx("strong", { children: project.name }),
7631
- "? This cannot be undone."
7632
- ] }),
7633
- /* @__PURE__ */ jsx(Divider, {}),
7634
- /* @__PURE__ */ jsxs(Group, { justify: "flex-end", children: [
7635
- /* @__PURE__ */ jsx(Button, { variant: "default", onClick: () => setDeleteModalOpen(false), disabled: isDeleting, children: "Cancel" }),
7636
- /* @__PURE__ */ jsx(
7637
- Button,
7638
- {
7639
- color: "red",
7640
- loading: isDeleting,
7641
- onClick: () => deleteProject(project.id, {
7642
- onSuccess: () => {
7643
- setDeleteModalOpen(false);
7644
- onBack();
7645
- }
7646
- }),
7647
- children: "Delete project"
7648
- }
7649
- )
7650
- ] })
7651
- ] }) })
7652
- ] }) }) });
7653
- }
7654
3778
  function NotificationPanel({ notifications, isLoading, onClose, onNavigate }) {
7655
3779
  const markAllAsRead = useMarkAllAsRead();
7656
3780
  const hasUnread = notifications.some((n) => !n.read);
@@ -7703,4 +3827,4 @@ function NotificationBell({ unreadCount, onNavigate }) {
7703
3827
  ] });
7704
3828
  }
7705
3829
 
7706
- export { AbsoluteScheduleForm, ActivityFeedWidget, ApiKeyDisplayModal, ApiKeyList, ApiKeySettings, Breadcrumbs, CommandViewEdge, CommandViewGraph, CommandViewNode, CrashErrorFallback, CreateApiKeyModal, CreateScheduleModal, CrmOverview, CrmSidebar, CrmSidebarMiddle, CrmSidebarTop, DEAL_STAGES, DEFAULT_KANBAN_CONFIG, DealDetailPage, DealDrawer, DealKanbanCard, DealsListPage, DeleteScheduleModal, DeploymentDetailModal, DeploymentList, DeploymentSettings, DeploymentStatusBadge, DocTreeNav, EditApiKeyModal, ErrorReportCard, HealthStatusCard, KanbanBoard, KnowledgeBasePage, LEAD_GEN_ROUTE_LINKS, LIST_TEMPLATE_OPTIONS, LeadGenCompaniesPage, LeadGenContactsPage, LeadGenDeliverabilityPage, LeadGenListDetailPage, LeadGenListsPage, LeadGenOverviewPage, LeadGenRouteShell, LeadGenSidebar, LeadGenSidebarMiddle, LeadGenSidebarTop, MdxRenderer, MetricsStrip, MilestoneTimeline, MyTasksPanel, NotificationBell, NotificationPanel, PIPELINE_FUNNEL_ORDER, PipelineFunnelWidget, ProjectDetailPage, ProjectsListPage, ProjectsSidebar, ProjectsSidebarMiddle, ProjectsSidebarTop, QuickCreateActions, RecurringScheduleForm, RelativeScheduleForm, RichTextEditor, SAVED_VIEW_PRESETS, SEOSidebar, SEOSidebarMiddle, SEOSidebarTop, SavedViewsPanel, ScheduleCard, ScheduleDetailModal, ScheduleTypeSelector, SortableHeader, TableSelectionToolbar, TaskCard, TaskScheduler, TasksDueWidget, buildErrorReport, buildListConfig, calculateProgress, crmManifest, deliveryManifest, formatStatusLabel, getEnrichmentColor, getStatusColor3 as getStatusColor, leadGenManifest, mdxComponents, milestoneStatusColors, noteTypeColors, projectStatusColors, seoManifest, taskStatusColors, taskTypeColors, useCrmPipelineSummary, useCrmQuickMetrics, useDeleteLists, useRecentCrmActivity };
3830
+ export { AbsoluteScheduleForm, ApiKeyDisplayModal, ApiKeyList, ApiKeySettings, Breadcrumbs, CommandViewEdge, CommandViewGraph, CommandViewNode, CrashErrorFallback, CreateApiKeyModal, CreateScheduleModal, DEAL_STAGES, DEFAULT_KANBAN_CONFIG, DealDrawer, DealKanbanCard, DeleteScheduleModal, DeploymentDetailModal, DeploymentList, DeploymentSettings, DeploymentStatusBadge, DocTreeNav, EditApiKeyModal, ErrorReportCard, KanbanBoard, KnowledgeBasePage, MdxRenderer, NotificationBell, NotificationPanel, RecurringScheduleForm, RelativeScheduleForm, RichTextEditor, ScheduleCard, ScheduleDetailModal, ScheduleTypeSelector, TaskScheduler, buildErrorReport, mdxComponents };