@elevasis/ui 1.24.2 → 1.25.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 (42) hide show
  1. package/dist/charts/index.js +2 -2
  2. package/dist/{chunk-HOYZWSNV.js → chunk-3EVTCVKR.js} +2 -2
  3. package/dist/{chunk-7TLPKXC2.js → chunk-7RS6VTAV.js} +57 -58
  4. package/dist/{chunk-TQBM3OEW.js → chunk-BS4J2LAW.js} +1 -1
  5. package/dist/{chunk-JR2C4XAN.js → chunk-CYXZHBP4.js} +202 -295
  6. package/dist/{chunk-OH74INP2.js → chunk-FEZZ3IDU.js} +434 -314
  7. package/dist/{chunk-IAZT3VO6.js → chunk-G25YWGUL.js} +4 -1
  8. package/dist/{chunk-KLZB3MNC.js → chunk-HYYI4ZFT.js} +6 -252
  9. package/dist/{chunk-CTF6FS2M.js → chunk-L3GVDMCA.js} +211 -1
  10. package/dist/{chunk-EDAYKRPJ.js → chunk-QDO6NF2I.js} +28 -1
  11. package/dist/{chunk-RNL2IC2Y.js → chunk-QNABH7YG.js} +3 -3
  12. package/dist/{chunk-FATKFO7X.js → chunk-R565P6XC.js} +691 -2
  13. package/dist/{chunk-5266RV46.js → chunk-RIL2CDFE.js} +3 -3
  14. package/dist/{chunk-TML32XBW.js → chunk-RMPXGBNI.js} +2 -2
  15. package/dist/{chunk-N5SDJP44.js → chunk-US4JUSI3.js} +4 -4
  16. package/dist/components/index.d.ts +3006 -193
  17. package/dist/components/index.js +1541 -26
  18. package/dist/features/auth/index.d.ts +108 -9
  19. package/dist/features/dashboard/index.js +8 -8
  20. package/dist/features/monitoring/index.js +19 -9
  21. package/dist/features/operations/index.d.ts +74 -10
  22. package/dist/features/operations/index.js +236 -53
  23. package/dist/features/settings/index.d.ts +108 -9
  24. package/dist/features/settings/index.js +28 -11
  25. package/dist/hooks/index.d.ts +3303 -193
  26. package/dist/hooks/index.js +5 -5
  27. package/dist/hooks/published.d.ts +134 -19
  28. package/dist/hooks/published.js +4 -4
  29. package/dist/index.d.ts +3309 -195
  30. package/dist/index.js +6 -6
  31. package/dist/initialization/index.d.ts +108 -9
  32. package/dist/layout/index.d.ts +60 -3
  33. package/dist/layout/index.js +2 -2
  34. package/dist/profile/index.d.ts +108 -9
  35. package/dist/provider/index.d.ts +6 -2
  36. package/dist/provider/index.js +3 -3
  37. package/dist/provider/published.d.ts +6 -2
  38. package/dist/supabase/index.d.ts +210 -18
  39. package/dist/theme/index.d.ts +6 -2
  40. package/dist/theme/index.js +3 -3
  41. package/dist/types/index.d.ts +108 -9
  42. package/package.json +3 -3
@@ -1,28 +1,28 @@
1
1
  import { useBreadcrumbs } from '../chunk-MG3NF7QL.js';
2
- import { SubshellContainer, SubshellSidebar, SubshellRightSideContainer, SubshellContentContainer } from '../chunk-CTF6FS2M.js';
3
- import { NotificationList } from '../chunk-7TLPKXC2.js';
4
- export { ActivityCard, ActivityFilters as ActivityFiltersBar, ActivityTable, BusinessImpactCard, CostBreakdownCard, CostByModelTable, CostMetricsCard, ErrorAnalysisCard, ErrorBreakdownTable, ExecutionBreakdownTable, ExecutionHealthCard, ExecutionLogsFilters as ExecutionLogsFilterBar, ExecutionLogsTable, NotificationItem, NotificationList } from '../chunk-7TLPKXC2.js';
5
- export { CreateCredentialModal, CredentialList, CredentialSettings, MembershipFeaturePanel, MembershipStatusBadge, OAuthConnectModal, OrganizationMembershipsList, WebhookUrlDisplayModal } from '../chunk-HOYZWSNV.js';
2
+ import { SubshellContainer, SubshellSidebar, SubshellRightSideContainer, SubshellContentContainer } from '../chunk-L3GVDMCA.js';
3
+ import { NotificationList } from '../chunk-7RS6VTAV.js';
4
+ export { ActivityCard, ActivityFilters as ActivityFiltersBar, ActivityTable, BusinessImpactCard, CostBreakdownCard, CostByModelTable, CostMetricsCard, ErrorAnalysisCard, ErrorBreakdownTable, ExecutionBreakdownTable, ExecutionHealthCard, ExecutionLogsFilters as ExecutionLogsFilterBar, ExecutionLogsTable, NotificationItem, NotificationList } from '../chunk-7RS6VTAV.js';
5
+ export { CreateCredentialModal, CredentialList, CredentialSettings, MembershipFeaturePanel, MembershipStatusBadge, OAuthConnectModal, OrganizationMembershipsList, WebhookUrlDisplayModal } from '../chunk-3EVTCVKR.js';
6
6
  import { FilterBar } from '../chunk-PDHTXPSF.js';
7
7
  export { FilterBar } from '../chunk-PDHTXPSF.js';
8
8
  export { ResourceHealthChart, getHealthColor } from '../chunk-LGKLC5MG.js';
9
- export { ActionModal, AgentDefinitionDisplay, AgentExecutionLogs, BaseExecutionLogs, BaseExecutionLogsHeader, BaseExecutionLogsStates, CheckpointGroup, CollapsibleJsonSection, CommandQueueSidebar, CommandQueueSidebarMiddle, CommandQueueSidebarTop, CommandQueueTaskRow, CommandViewEdge, CommandViewGraph, CommandViewNode, ConfigCard, ContentSections, ContextUsageBadge, ContractDisplay, ExecutionErrorSection, FormFieldRenderer, LogEntry, LogGroup, NewKnowledgeMapEdge, NewKnowledgeMapGraph, NewKnowledgeMapNode, ResourceDefinitionSection, ResourceErrorState, ResourceFilter, ResourceHeader, ResourceNotFoundState, SessionMemory, TaskCard, ToolsListDisplay, WorkflowDefinitionDisplay, WorkflowExecutionLogs, getExecutionStatusConfig, getIcon, getLogLevelConfig, iconMap, useNewKnowledgeMapLayout } from '../chunk-KLZB3MNC.js';
10
- import { SubshellLoader, PageContainer, CollapsibleSidebarGroup, SidebarListItem } from '../chunk-AWT255UH.js';
11
- export { ResourceHealthPanel } from '../chunk-5266RV46.js';
9
+ export { ActionModal, AgentDefinitionDisplay, AgentExecutionLogs, BaseExecutionLogs, BaseExecutionLogsHeader, BaseExecutionLogsStates, CheckpointGroup, CollapsibleJsonSection, CommandQueueSidebar, CommandQueueSidebarMiddle, CommandQueueSidebarTop, CommandQueueTaskRow, CommandViewEdge, CommandViewGraph, CommandViewNode, ConfigCard, ContentSections, ContextUsageBadge, ContractDisplay, ExecutionErrorSection, FormFieldRenderer, LogEntry, LogGroup, NewKnowledgeMapEdge, NewKnowledgeMapGraph, NewKnowledgeMapNode, ResourceDefinitionSection, ResourceErrorState, ResourceFilter, ResourceHeader, ResourceNotFoundState, SessionMemory, ToolsListDisplay, WorkflowDefinitionDisplay, WorkflowExecutionLogs, getExecutionStatusConfig, getIcon, getLogLevelConfig, iconMap, useNewKnowledgeMapLayout } from '../chunk-HYYI4ZFT.js';
10
+ import { SubshellLoader, PageContainer, SubshellSidebarSection, SidebarListItem, CollapsibleSidebarGroup } from '../chunk-AWT255UH.js';
11
+ export { ResourceHealthPanel } from '../chunk-RIL2CDFE.js';
12
12
  import { CustomModal } from '../chunk-GBMNCNHX.js';
13
13
  export { ConfirmationInputModal, ConfirmationModal, CustomModal } from '../chunk-GBMNCNHX.js';
14
- export { AgentExecutionTimeline, AgentExecutionVisualizer, AgentIterationEdge, AgentIterationNode, BaseEdge, BaseNode, EmptyVisualizer, ExecutionStats, ExecutionStatusBadge, GraphBackground, GraphContainer, GraphFitViewButton, GraphFitViewHandler, GraphLegend, TimelineAxis, TimelineBar, TimelineContainer, TimelineRow, UnifiedWorkflowEdge, UnifiedWorkflowGraph, UnifiedWorkflowNode, VisualizerContainer, WorkflowExecutionTimeline, getGraphBackgroundStyles, useGraphBackgroundStyles, useGraphTheme } from '../chunk-RNL2IC2Y.js';
15
- import '../chunk-N5SDJP44.js';
16
- import { ListSkeleton, EmptyState, PageTitleCaption, StatCard, APIErrorAlert, CardHeader } from '../chunk-IAZT3VO6.js';
17
- export { APIErrorAlert, ActivityTimeline, CardHeader, CollapsibleSection, ContextViewer, CustomSelector, DetailCardSkeleton, EmptyState, GlowDot, JsonViewer, ListSkeleton, PageNotFound, PageTitleCaption, ResourceCard, StatCard, StatCardSkeleton, StatsCardSkeleton, StatusBadge, TabCountBadge, TimeRangeSelector, TrendIndicator, catalogItemToResourceDefinition } from '../chunk-IAZT3VO6.js';
14
+ export { AgentExecutionTimeline, AgentExecutionVisualizer, AgentIterationEdge, AgentIterationNode, BaseEdge, BaseNode, EmptyVisualizer, ExecutionStats, ExecutionStatusBadge, GraphBackground, GraphContainer, GraphFitViewButton, GraphFitViewHandler, GraphLegend, TimelineAxis, TimelineBar, TimelineContainer, TimelineRow, UnifiedWorkflowEdge, UnifiedWorkflowGraph, UnifiedWorkflowNode, VisualizerContainer, WorkflowExecutionTimeline, getGraphBackgroundStyles, useGraphBackgroundStyles, useGraphTheme } from '../chunk-QNABH7YG.js';
15
+ import '../chunk-US4JUSI3.js';
16
+ import { ListSkeleton, EmptyState, PageTitleCaption, StatCard, CenteredErrorState, CardHeader, ActivityTimeline, StatusBadge } from '../chunk-G25YWGUL.js';
17
+ export { APIErrorAlert, ActivityTimeline, CardHeader, CenteredErrorState, CollapsibleSection, ContextViewer, CustomSelector, DetailCardSkeleton, EmptyState, GlowDot, JsonViewer, ListSkeleton, PageNotFound, PageTitleCaption, ResourceCard, StatCard, StatCardSkeleton, StatsCardSkeleton, StatusBadge, TabCountBadge, TimeRangeSelector, TrendIndicator, catalogItemToResourceDefinition } from '../chunk-G25YWGUL.js';
18
18
  export { StyledMarkdown } from '../chunk-3KMDHCAR.js';
19
19
  export { NavigationButton } from '../chunk-NNKKBSJN.js';
20
20
  import { AppShellLoader } from '../chunk-WWEMNIHW.js';
21
21
  import '../chunk-QJ2S46NI.js';
22
- import { useUpdateApiKey, useDeleteApiKey, useCreateApiKey, useListApiKeys, useActivateDeployment, useDeactivateDeployment, useDeleteDeployment, useListDeployments } from '../chunk-FATKFO7X.js';
23
- import { usePaginationState, useDeploymentDocs, useResources, useCreateSchedule, useListSchedules, usePauseSchedule, useResumeSchedule, useCancelSchedule, useDeleteSchedule, useMarkAllAsRead, useNotifications, showErrorNotification, showSuccessNotification, showApiErrorNotification } from '../chunk-EDAYKRPJ.js';
24
- export { showApiErrorNotification, showErrorNotification, showInfoNotification, showSuccessNotification, showWarningNotification } from '../chunk-EDAYKRPJ.js';
25
- import '../chunk-NJJ3NQ7B.js';
22
+ import { useUpdateApiKey, useDeleteApiKey, useCreateApiKey, useListApiKeys, useActivateDeployment, useDeactivateDeployment, useDeleteDeployment, useListDeployments, useDealNotes, useCreateDealNote, useDeals, useSyncDealStage, dealKeys, useDealTasksDue, useCreateDealTask, useTasks, useProjects, useMilestones } from '../chunk-R565P6XC.js';
23
+ import { usePaginationState, useDeploymentDocs, useResources, useCreateSchedule, useListSchedules, usePauseSchedule, useResumeSchedule, useCancelSchedule, useDeleteSchedule, useMarkAllAsRead, useNotifications, showErrorNotification, showSuccessNotification, showApiErrorNotification } from '../chunk-QDO6NF2I.js';
24
+ export { showApiErrorNotification, showErrorNotification, showInfoNotification, showSuccessNotification, showWarningNotification } from '../chunk-QDO6NF2I.js';
25
+ import { useSupabase } from '../chunk-NJJ3NQ7B.js';
26
26
  import '../chunk-LXHZYSMQ.js';
27
27
  import '../chunk-MHW43EOH.js';
28
28
  export { Graph_module_css_default as graphStyles } from '../chunk-F6RBK7NJ.js';
@@ -31,28 +31,28 @@ import '../chunk-ELJIFLCB.js';
31
31
  import '../chunk-L4XXM55J.js';
32
32
  import '../chunk-SLVC5OJ2.js';
33
33
  import '../chunk-RNP5R5I3.js';
34
- import '../chunk-TML32XBW.js';
34
+ import '../chunk-RMPXGBNI.js';
35
35
  export { ElevasisLoader } from '../chunk-SZHARWKU.js';
36
- import '../chunk-OH74INP2.js';
37
- import '../chunk-JR2C4XAN.js';
36
+ import '../chunk-FEZZ3IDU.js';
37
+ import '../chunk-CYXZHBP4.js';
38
38
  import '../chunk-R7WLWGPO.js';
39
39
  import '../chunk-NVOCKXUQ.js';
40
40
  import '../chunk-V7XHGJQZ.js';
41
41
  import { useAppearance } from '../chunk-QJ2KCHKX.js';
42
- import { formatDateTime, PAGE_SIZE_DEFAULT } from '../chunk-IOKL7BKE.js';
42
+ import { formatDateTime, PAGE_SIZE_DEFAULT, formatTimeAgo } from '../chunk-IOKL7BKE.js';
43
43
  import '../chunk-RWQIFKMJ.js';
44
44
  import '../chunk-ALA56RGZ.js';
45
45
  import { useInitialization } from '../chunk-TUXTSEAF.js';
46
- import '../chunk-DD3CCMCZ.js';
46
+ import { useOrganization } from '../chunk-DD3CCMCZ.js';
47
47
  import { useElevasisServices } from '../chunk-QEPXAWE2.js';
48
48
  import '../chunk-BRJ3QZ4E.js';
49
49
  import { useRouterContext } from '../chunk-Q7DJKLEN.js';
50
- import { Table, Group, Text, Button, Stack, Title, TextInput, Alert, Tooltip, ActionIcon, Paper, Code, CopyButton, SimpleGrid, Badge, Loader, Pagination, useMantineTheme, Box, ScrollArea, Select, Center, Card, SegmentedControl, Switch, Textarea, Divider, Menu, Timeline, ThemeIcon, Tabs, Anchor, Breadcrumbs as Breadcrumbs$1, Popover, Indicator } from '@mantine/core';
51
- import { IconChevronUp, IconChevronDown, IconSelector, IconTrash, IconPencil, IconAlertCircle, IconKey, IconCalendar, IconClock, IconAlertTriangle, IconExclamationMark, IconShieldLock, IconCheck, IconCopy, IconPlus, IconRocket, IconRefresh, IconPower, IconPlayerPlay, IconCircleCheck, IconTag, IconBook2, IconFileOff, IconList, IconCalendarRepeat, IconCalendarEvent, IconCalendarTime, IconRobot, IconGitBranch, IconSettings, IconExternalLink, IconDotsVertical, IconPlayerPause, IconPlayerStop, IconCalendarDue, IconCalendarStats, IconCalendarOff, IconListCheck, IconBell, IconFolderOpen, IconFolder, IconFileText } from '@tabler/icons-react';
50
+ import { Table, Group, Text, Button, Stack, Title, TextInput, Alert, Tooltip, ActionIcon, Paper, Code, CopyButton, SimpleGrid, Badge, Loader, Pagination, useMantineTheme, Box, ScrollArea, Select, Center, Card, SegmentedControl, Switch, Textarea, Divider, Menu, Timeline, ThemeIcon, Tabs, Anchor, Breadcrumbs as Breadcrumbs$1, Drawer, UnstyledButton, Modal, RingProgress, Collapse, Popover, Indicator } from '@mantine/core';
51
+ import { IconChevronUp, IconChevronDown, IconSelector, IconTrash, IconPencil, IconAlertCircle, IconKey, IconCalendar, IconClock, IconAlertTriangle, IconExclamationMark, IconShieldLock, IconCheck, IconCopy, IconPlus, IconRocket, IconRefresh, IconPower, IconPlayerPlay, IconCircleCheck, IconTag, IconBook2, IconFileOff, IconList, IconCalendarRepeat, IconCalendarEvent, IconCalendarTime, IconRobot, IconGitBranch, IconSettings, IconExternalLink, IconDotsVertical, IconPlayerPause, IconPlayerStop, IconCalendarDue, IconCalendarStats, IconCalendarOff, IconListCheck, IconColumns, IconChecklist, IconHistory, IconTrophy, IconClockExclamation, IconUser, IconHeartbeat, IconFlag, IconFileText, IconInbox, IconLock, IconChevronRight, IconDownload, IconBriefcase, IconMessageCircle, IconBell, IconNotes, IconFolderOpen, IconFolder, IconCheckbox, IconMail, IconPhone, IconArrowRight, IconNote } from '@tabler/icons-react';
52
52
  import * as runtime from 'react/jsx-runtime';
53
53
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
54
54
  import { useDisclosure } from '@mantine/hooks';
55
- import { useQuery } from '@tanstack/react-query';
55
+ import { useQueryClient, useQuery } from '@tanstack/react-query';
56
56
  import { useState, useEffect, useMemo, useCallback, useRef } from 'react';
57
57
  import { useForm } from '@mantine/form';
58
58
  import { Link, RichTextEditor as RichTextEditor$1 } from '@mantine/tiptap';
@@ -2212,7 +2212,7 @@ var TaskScheduler = () => {
2212
2212
  return buckets;
2213
2213
  };
2214
2214
  if (error) {
2215
- return /* @__PURE__ */ jsx(APIErrorAlert, { error, title: "Failed to load schedules" });
2215
+ return /* @__PURE__ */ jsx(Paper, { children: /* @__PURE__ */ jsx(CenteredErrorState, { error, title: "Failed to load schedules" }) });
2216
2216
  }
2217
2217
  const timeBuckets = groupByTimeBucket(filteredSchedules);
2218
2218
  const renderScheduleCards = (schedules) => schedules.map((schedule) => /* @__PURE__ */ jsx(
@@ -2229,7 +2229,7 @@ var TaskScheduler = () => {
2229
2229
  },
2230
2230
  schedule.id
2231
2231
  ));
2232
- return /* @__PURE__ */ jsxs(Fragment, { children: [
2232
+ return /* @__PURE__ */ jsxs(Stack, { children: [
2233
2233
  /* @__PURE__ */ jsx(
2234
2234
  PageTitleCaption,
2235
2235
  {
@@ -3045,6 +3045,1521 @@ var Breadcrumbs = ({ navItems }) => {
3045
3045
  });
3046
3046
  return /* @__PURE__ */ jsx(Breadcrumbs$1, { separator: "/", children: items });
3047
3047
  };
3048
+ function DealKanbanCard({ deal, config: _config, onClick, onDragStart, onDragEnd }) {
3049
+ const contactFirstName = deal.contact?.first_name || "";
3050
+ const contactLastName = deal.contact?.last_name || "";
3051
+ const contactName = [contactFirstName, contactLastName].filter(Boolean).join(" ");
3052
+ const displayName = contactName || deal.contact_email || "Unknown";
3053
+ const companyName = deal.contact?.company?.name || deal.discovery_data?.company || deal.contact_email?.split("@")[1] || null;
3054
+ const hasInitialFee = typeof deal.initial_fee === "number" && deal.initial_fee > 0;
3055
+ const hasMonthlyFee = typeof deal.monthly_fee === "number" && deal.monthly_fee > 0;
3056
+ return /* @__PURE__ */ jsx(
3057
+ Card,
3058
+ {
3059
+ padding: "sm",
3060
+ style: {
3061
+ cursor: "grab",
3062
+ userSelect: "none"
3063
+ },
3064
+ withBorder: true,
3065
+ draggable: true,
3066
+ onDragStart: (e) => onDragStart(e, deal),
3067
+ onDragEnd,
3068
+ onClick: () => onClick(deal),
3069
+ children: /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
3070
+ /* @__PURE__ */ jsx(Text, { size: "sm", fw: 600, lineClamp: 1, children: displayName }),
3071
+ companyName && /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", lineClamp: 1, children: companyName }),
3072
+ /* @__PURE__ */ jsxs(Group, { gap: 4, wrap: "wrap", children: [
3073
+ hasInitialFee && /* @__PURE__ */ jsxs(Badge, { variant: "outline", size: "xs", color: "green", children: [
3074
+ "$",
3075
+ deal.initial_fee.toLocaleString()
3076
+ ] }),
3077
+ hasMonthlyFee && /* @__PURE__ */ jsxs(Badge, { variant: "outline", size: "xs", color: "teal", children: [
3078
+ "$",
3079
+ deal.monthly_fee.toLocaleString(),
3080
+ "/mo"
3081
+ ] })
3082
+ ] }),
3083
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: formatTimeAgo(deal.updated_at) })
3084
+ ] })
3085
+ }
3086
+ );
3087
+ }
3088
+
3089
+ // src/components/acquisition/kanban/constants.ts
3090
+ var DEAL_STAGES = [
3091
+ "interested",
3092
+ "booked",
3093
+ "qualified",
3094
+ "demo_booked",
3095
+ "proposal",
3096
+ "proposal_sent",
3097
+ "proposal_signed",
3098
+ "payment_sent",
3099
+ "proposal_revision",
3100
+ "closed_won",
3101
+ "closed_lost",
3102
+ "nurturing",
3103
+ "cancelled",
3104
+ "no_show"
3105
+ ];
3106
+ var DEFAULT_KANBAN_CONFIG = {
3107
+ interested: { color: "blue" },
3108
+ booked: { color: "cyan" },
3109
+ qualified: { color: "teal" },
3110
+ demo_booked: { color: "indigo" },
3111
+ proposal: { color: "yellow" },
3112
+ proposal_sent: { color: "orange" },
3113
+ proposal_signed: { color: "lime" },
3114
+ payment_sent: { color: "yellow" },
3115
+ proposal_revision: { color: "pink" },
3116
+ closed_won: { color: "green" },
3117
+ closed_lost: { color: "red" },
3118
+ nurturing: { color: "grape" },
3119
+ cancelled: { color: "gray" },
3120
+ no_show: { color: "gray" }
3121
+ };
3122
+ function formatStageLabel(stage) {
3123
+ if (!stage) return "Unstaged";
3124
+ return stage.split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
3125
+ }
3126
+ function DealDrawer({ deal, opened, onClose, renderActions }) {
3127
+ const [noteBody, setNoteBody] = useState("");
3128
+ const { data: notes, isLoading: notesLoading } = useDealNotes(deal?.id ?? "");
3129
+ const createNote = useCreateDealNote();
3130
+ if (!deal) return null;
3131
+ const contactFirstName = deal.contact?.first_name || "";
3132
+ const contactLastName = deal.contact?.last_name || "";
3133
+ const contactName = [contactFirstName, contactLastName].filter(Boolean).join(" ");
3134
+ const displayName = contactName || deal.contact_email || "Unknown";
3135
+ const email = deal.contact?.email || deal.contact_email || null;
3136
+ const companyName = deal.contact?.company?.name || deal.discovery_data?.company || null;
3137
+ const stage = deal.cached_stage;
3138
+ const badgeColor = DEFAULT_KANBAN_CONFIG[stage]?.color ?? "gray";
3139
+ const activityLog = deal.activity_log || [];
3140
+ const hasInitialFee = typeof deal.initial_fee === "number" && deal.initial_fee > 0;
3141
+ const hasMonthlyFee = typeof deal.monthly_fee === "number" && deal.monthly_fee > 0;
3142
+ function handleAddNote() {
3143
+ if (!noteBody.trim() || !deal) return;
3144
+ createNote.mutate(
3145
+ { dealId: deal.id, body: noteBody.trim() },
3146
+ {
3147
+ onSuccess: () => setNoteBody("")
3148
+ }
3149
+ );
3150
+ }
3151
+ return /* @__PURE__ */ jsx(
3152
+ Drawer,
3153
+ {
3154
+ opened,
3155
+ onClose,
3156
+ position: "right",
3157
+ size: "lg",
3158
+ title: /* @__PURE__ */ jsxs(Group, { gap: "sm", wrap: "nowrap", children: [
3159
+ /* @__PURE__ */ jsx(Title, { order: 4, style: { margin: 0 }, children: displayName }),
3160
+ /* @__PURE__ */ jsx(Badge, { variant: "light", color: badgeColor, size: "sm", children: formatStageLabel(stage) })
3161
+ ] }),
3162
+ scrollAreaComponent: ScrollArea.Autosize,
3163
+ children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
3164
+ email && /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: email }),
3165
+ companyName && /* @__PURE__ */ jsx(Text, { size: "sm", fw: 500, children: companyName }),
3166
+ /* @__PURE__ */ jsx(Divider, {}),
3167
+ /* @__PURE__ */ jsxs(Stack, { gap: 6, children: [
3168
+ /* @__PURE__ */ jsx(Title, { order: 5, children: "Summary" }),
3169
+ /* @__PURE__ */ jsxs(Group, { gap: "xl", wrap: "wrap", children: [
3170
+ hasInitialFee && /* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
3171
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Initial Fee" }),
3172
+ /* @__PURE__ */ jsxs(Text, { size: "sm", fw: 500, children: [
3173
+ "$",
3174
+ deal.initial_fee.toLocaleString()
3175
+ ] })
3176
+ ] }),
3177
+ hasMonthlyFee && /* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
3178
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Monthly Fee" }),
3179
+ /* @__PURE__ */ jsxs(Text, { size: "sm", fw: 500, children: [
3180
+ "$",
3181
+ deal.monthly_fee.toLocaleString(),
3182
+ "/mo"
3183
+ ] })
3184
+ ] }),
3185
+ /* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
3186
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Last Updated" }),
3187
+ /* @__PURE__ */ jsx(Text, { size: "sm", fw: 500, children: formatTimeAgo(deal.updated_at) })
3188
+ ] }),
3189
+ /* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
3190
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Deal ID" }),
3191
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", ff: "monospace", children: deal.id })
3192
+ ] })
3193
+ ] })
3194
+ ] }),
3195
+ renderActions && renderActions(),
3196
+ /* @__PURE__ */ jsx(Divider, {}),
3197
+ /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
3198
+ /* @__PURE__ */ jsx(Title, { order: 5, children: "Notes" }),
3199
+ /* @__PURE__ */ jsxs(Stack, { gap: "xs", children: [
3200
+ /* @__PURE__ */ jsx(
3201
+ Textarea,
3202
+ {
3203
+ placeholder: "Add a note...",
3204
+ minRows: 2,
3205
+ autosize: true,
3206
+ value: noteBody,
3207
+ onChange: (e) => setNoteBody(e.currentTarget.value)
3208
+ }
3209
+ ),
3210
+ /* @__PURE__ */ jsx(Group, { justify: "flex-end", children: /* @__PURE__ */ jsx(
3211
+ Button,
3212
+ {
3213
+ size: "xs",
3214
+ variant: "light",
3215
+ loading: createNote.isPending,
3216
+ disabled: !noteBody.trim(),
3217
+ onClick: handleAddNote,
3218
+ children: "Add note"
3219
+ }
3220
+ ) })
3221
+ ] }),
3222
+ notesLoading ? /* @__PURE__ */ jsx(Center, { p: "sm", children: /* @__PURE__ */ jsx(Loader, { size: "sm" }) }) : notes && notes.length > 0 ? /* @__PURE__ */ jsx(Stack, { gap: "xs", children: notes.map((note) => /* @__PURE__ */ jsxs(
3223
+ Stack,
3224
+ {
3225
+ gap: 4,
3226
+ style: {
3227
+ padding: "8px 12px",
3228
+ borderRadius: 6,
3229
+ backgroundColor: "var(--color-surface)",
3230
+ border: "1px solid var(--color-border)"
3231
+ },
3232
+ children: [
3233
+ /* @__PURE__ */ jsx(Text, { size: "sm", children: note.body }),
3234
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: formatTimeAgo(note.createdAt) })
3235
+ ]
3236
+ },
3237
+ note.id
3238
+ )) }) : /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "No notes yet." })
3239
+ ] }),
3240
+ /* @__PURE__ */ jsx(Divider, {}),
3241
+ /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
3242
+ /* @__PURE__ */ jsx(Title, { order: 5, children: "Activity" }),
3243
+ /* @__PURE__ */ jsx(ActivityTimeline, { activities: activityLog })
3244
+ ] })
3245
+ ] })
3246
+ }
3247
+ );
3248
+ }
3249
+ var UNSTAGED_KEY = "unstaged";
3250
+ function formatStageLabel2(stage) {
3251
+ if (stage === UNSTAGED_KEY) return "Unstaged";
3252
+ return stage.split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
3253
+ }
3254
+ function groupDealsByStage(deals) {
3255
+ const map = /* @__PURE__ */ new Map();
3256
+ for (const stage of DEAL_STAGES) {
3257
+ map.set(stage, []);
3258
+ }
3259
+ map.set(UNSTAGED_KEY, []);
3260
+ for (const deal of deals) {
3261
+ const key = deal.cached_stage ?? UNSTAGED_KEY;
3262
+ const bucket = map.get(key);
3263
+ if (bucket) {
3264
+ bucket.push(deal);
3265
+ } else {
3266
+ map.get(UNSTAGED_KEY).push(deal);
3267
+ }
3268
+ }
3269
+ return map;
3270
+ }
3271
+ function KanbanBoard({ config = DEFAULT_KANBAN_CONFIG, renderDrawerActions }) {
3272
+ const { data: deals, isLoading, error } = useDeals();
3273
+ const syncStage = useSyncDealStage();
3274
+ const queryClient = useQueryClient();
3275
+ const [selectedDeal, setSelectedDeal] = useState(null);
3276
+ const [drawerOpen, setDrawerOpen] = useState(false);
3277
+ const [dragOverColumn, setDragOverColumn] = useState(null);
3278
+ const draggingDealRef = useRef(null);
3279
+ const groupedDeals = groupDealsByStage(deals ?? []);
3280
+ const columns = [...DEAL_STAGES, UNSTAGED_KEY];
3281
+ const handleDragStart = useCallback((e, deal) => {
3282
+ draggingDealRef.current = deal;
3283
+ e.dataTransfer.effectAllowed = "move";
3284
+ e.dataTransfer.setData("text/plain", deal.id);
3285
+ }, []);
3286
+ const handleDragOver = useCallback((e, column) => {
3287
+ if (column === UNSTAGED_KEY) {
3288
+ e.dataTransfer.dropEffect = "none";
3289
+ return;
3290
+ }
3291
+ e.preventDefault();
3292
+ e.dataTransfer.dropEffect = "move";
3293
+ setDragOverColumn(column);
3294
+ }, []);
3295
+ const handleDragLeave = useCallback(() => {
3296
+ setDragOverColumn(null);
3297
+ }, []);
3298
+ const handleDrop = useCallback(
3299
+ (e, targetStage) => {
3300
+ e.preventDefault();
3301
+ setDragOverColumn(null);
3302
+ if (targetStage === UNSTAGED_KEY) return;
3303
+ const deal = draggingDealRef.current;
3304
+ draggingDealRef.current = null;
3305
+ if (!deal) return;
3306
+ if (deal.cached_stage === targetStage) return;
3307
+ queryClient.setQueriesData({ queryKey: dealKeys.lists() }, (old) => {
3308
+ if (!old) return old;
3309
+ return old.map((d) => d.id === deal.id ? { ...d, cached_stage: targetStage } : d);
3310
+ });
3311
+ syncStage.mutate({ dealId: deal.id, stage: targetStage });
3312
+ },
3313
+ [queryClient, syncStage]
3314
+ );
3315
+ const handleDragEnd = useCallback(() => {
3316
+ draggingDealRef.current = null;
3317
+ setDragOverColumn(null);
3318
+ }, []);
3319
+ const handleCardClick = useCallback((deal) => {
3320
+ setSelectedDeal(deal);
3321
+ setDrawerOpen(true);
3322
+ }, []);
3323
+ if (isLoading) {
3324
+ return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsx(Center, { style: { flex: 1 }, children: /* @__PURE__ */ jsx(Loader, {}) }) });
3325
+ }
3326
+ if (error) {
3327
+ return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsx(Alert, { color: "red", children: "Failed to load deals" }) });
3328
+ }
3329
+ return /* @__PURE__ */ jsxs(SubshellContentContainer, { children: [
3330
+ /* @__PURE__ */ jsxs(Stack, { gap: "md", style: { height: "100%" }, children: [
3331
+ /* @__PURE__ */ jsx(PageTitleCaption, { title: "Pipeline", caption: "Kanban view of your deal pipeline" }),
3332
+ /* @__PURE__ */ jsx(ScrollArea, { style: { flex: 1 }, scrollbarSize: 8, type: "auto", children: /* @__PURE__ */ jsx(Group, { align: "flex-start", wrap: "nowrap", gap: "sm", style: { paddingBottom: 16, minWidth: "max-content" }, children: columns.map((columnKey) => {
3333
+ const columnDeals = groupedDeals.get(columnKey) ?? [];
3334
+ const isUnstaged = columnKey === UNSTAGED_KEY;
3335
+ const isDragTarget = dragOverColumn === columnKey;
3336
+ const badgeColor = isUnstaged ? "gray" : config[columnKey]?.color ?? "gray";
3337
+ return /* @__PURE__ */ jsx(
3338
+ Paper,
3339
+ {
3340
+ withBorder: true,
3341
+ style: {
3342
+ width: 300,
3343
+ minWidth: 300,
3344
+ backgroundColor: isDragTarget ? "color-mix(in srgb, var(--color-primary) 8%, var(--glass-background))" : "var(--glass-background)",
3345
+ border: isDragTarget ? "1px solid var(--color-primary)" : "1px solid var(--color-border)",
3346
+ transition: "background-color var(--duration-fast) var(--easing), border-color var(--duration-fast) var(--easing)"
3347
+ },
3348
+ onDragOver: (e) => handleDragOver(e, columnKey),
3349
+ onDragLeave: handleDragLeave,
3350
+ onDrop: (e) => handleDrop(e, columnKey),
3351
+ children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", p: "sm", children: [
3352
+ /* @__PURE__ */ jsxs(Group, { justify: "space-between", wrap: "nowrap", children: [
3353
+ /* @__PURE__ */ jsx(Text, { size: "sm", fw: 600, tt: "uppercase", lineClamp: 1, children: formatStageLabel2(columnKey) }),
3354
+ /* @__PURE__ */ jsx(Badge, { variant: "light", color: badgeColor, size: "sm", style: { flexShrink: 0 }, children: columnDeals.length })
3355
+ ] }),
3356
+ columnDeals.length === 0 && /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", ta: "center", py: "md", style: { opacity: 0.5 }, children: isUnstaged ? "No unstaged deals" : "Drop here" }),
3357
+ /* @__PURE__ */ jsx(Stack, { gap: "xs", children: columnDeals.map((deal) => /* @__PURE__ */ jsx(
3358
+ DealKanbanCard,
3359
+ {
3360
+ deal,
3361
+ config,
3362
+ onClick: handleCardClick,
3363
+ onDragStart: handleDragStart,
3364
+ onDragEnd: handleDragEnd
3365
+ },
3366
+ deal.id
3367
+ )) })
3368
+ ] })
3369
+ },
3370
+ columnKey
3371
+ );
3372
+ }) }) })
3373
+ ] }),
3374
+ /* @__PURE__ */ jsx(
3375
+ DealDrawer,
3376
+ {
3377
+ deal: selectedDeal,
3378
+ opened: drawerOpen,
3379
+ onClose: () => setDrawerOpen(false),
3380
+ renderActions: renderDrawerActions
3381
+ }
3382
+ )
3383
+ ] });
3384
+ }
3385
+ var PIPELINE_FUNNEL_ORDER = [
3386
+ "interested",
3387
+ "booked",
3388
+ "qualified",
3389
+ "demo_booked",
3390
+ "proposal",
3391
+ "proposal_sent",
3392
+ "proposal_signed",
3393
+ "payment_sent",
3394
+ "closed_won",
3395
+ "proposal_revision",
3396
+ "closed_lost",
3397
+ "nurturing",
3398
+ "cancelled",
3399
+ "no_show"
3400
+ ];
3401
+ var defaultValueOf = (deal) => deal.initial_fee ?? 0;
3402
+ function useCrmPipelineSummary(opts) {
3403
+ const getValue = opts?.getDealValue ?? defaultValueOf;
3404
+ const { data: deals, isLoading, error } = useDeals();
3405
+ const data = useMemo(() => {
3406
+ const dealList = deals ?? [];
3407
+ const stageMap = /* @__PURE__ */ new Map();
3408
+ for (const stage of PIPELINE_FUNNEL_ORDER) {
3409
+ stageMap.set(stage, { count: 0, totalValue: 0 });
3410
+ }
3411
+ for (const deal of dealList) {
3412
+ const stage = deal.cached_stage;
3413
+ if (!stage || !stageMap.has(stage)) continue;
3414
+ const entry = stageMap.get(stage);
3415
+ entry.count += 1;
3416
+ entry.totalValue += getValue(deal);
3417
+ }
3418
+ return PIPELINE_FUNNEL_ORDER.map((stage) => {
3419
+ const entry = stageMap.get(stage);
3420
+ return { stage, count: entry.count, totalValue: entry.totalValue };
3421
+ });
3422
+ }, [deals, getValue]);
3423
+ return { data, isLoading, error };
3424
+ }
3425
+ var CLOSED_STAGES = ["closed_won", "closed_lost", "cancelled"];
3426
+ var OPEN_EXCLUDED = CLOSED_STAGES;
3427
+ var ZERO_METRICS = {
3428
+ totalDeals: 0,
3429
+ openDeals: 0,
3430
+ wonDeals: 0,
3431
+ winRate: 0,
3432
+ avgDealSize: 0,
3433
+ totalPipelineValue: 0
3434
+ };
3435
+ function useCrmQuickMetrics() {
3436
+ const { data: deals, isLoading, error } = useDeals();
3437
+ const data = useMemo(() => {
3438
+ const dealList = deals ?? [];
3439
+ if (dealList.length === 0) return ZERO_METRICS;
3440
+ let openDeals = 0;
3441
+ let wonDeals = 0;
3442
+ let lostDeals = 0;
3443
+ let wonFeeSum = 0;
3444
+ let wonFeeCount = 0;
3445
+ let pipelineValue = 0;
3446
+ for (const deal of dealList) {
3447
+ const stage = deal.cached_stage;
3448
+ const isOpen = !stage || !OPEN_EXCLUDED.includes(stage);
3449
+ const isWon = stage === "closed_won";
3450
+ const isLost = stage === "closed_lost";
3451
+ if (isOpen) {
3452
+ openDeals += 1;
3453
+ pipelineValue += deal.initial_fee ?? 0;
3454
+ }
3455
+ if (isWon) {
3456
+ wonDeals += 1;
3457
+ if (deal.initial_fee != null) {
3458
+ wonFeeSum += deal.initial_fee;
3459
+ wonFeeCount += 1;
3460
+ }
3461
+ }
3462
+ if (isLost) {
3463
+ lostDeals += 1;
3464
+ }
3465
+ }
3466
+ const winRateDenominator = wonDeals + lostDeals;
3467
+ const winRate = winRateDenominator === 0 ? 0 : wonDeals / winRateDenominator;
3468
+ const avgDealSize = wonFeeCount === 0 ? 0 : wonFeeSum / wonFeeCount;
3469
+ return {
3470
+ totalDeals: dealList.length,
3471
+ openDeals,
3472
+ wonDeals,
3473
+ winRate,
3474
+ avgDealSize,
3475
+ totalPipelineValue: pipelineValue
3476
+ };
3477
+ }, [deals]);
3478
+ return { data, isLoading, error };
3479
+ }
3480
+ var RECENT_NOTES_LIMIT = 50;
3481
+ function useRecentCrmActivity(opts) {
3482
+ const limit = opts?.limit ?? 25;
3483
+ const supabase = useSupabase();
3484
+ const { currentSupabaseOrganizationId: organizationId, isInitializing, isOrgRefreshing } = useOrganization();
3485
+ const isReady = !!organizationId && !isInitializing && !isOrgRefreshing;
3486
+ const { data: deals, isLoading: dealsLoading, error: dealsError } = useDeals();
3487
+ const {
3488
+ data: rawNotes,
3489
+ isLoading: notesLoading,
3490
+ error: notesError
3491
+ } = useQuery({
3492
+ queryKey: ["crm-recent-notes", organizationId],
3493
+ queryFn: async () => {
3494
+ if (!organizationId) return [];
3495
+ const { data: data2, error } = await supabase.from("acq_deal_notes").select("id, deal_id, organization_id, body, created_at").eq("organization_id", organizationId).order("created_at", { ascending: false }).limit(RECENT_NOTES_LIMIT);
3496
+ if (error) throw error;
3497
+ return data2 ?? [];
3498
+ },
3499
+ enabled: isReady
3500
+ });
3501
+ const data = useMemo(() => {
3502
+ const dealList = deals ?? [];
3503
+ const noteList = rawNotes ?? [];
3504
+ const dealById = /* @__PURE__ */ new Map();
3505
+ for (const deal of dealList) {
3506
+ dealById.set(deal.id, deal);
3507
+ }
3508
+ const entries = [];
3509
+ for (const note of noteList) {
3510
+ const deal = dealById.get(note.deal_id);
3511
+ const contact = deal?.contact ?? null;
3512
+ const contactName = contact ? [contact.first_name, contact.last_name].filter(Boolean).join(" ") || null : null;
3513
+ const companyName = contact?.company?.name ?? null;
3514
+ entries.push({
3515
+ id: `note-${note.id}`,
3516
+ kind: "note",
3517
+ dealId: note.deal_id,
3518
+ occurredAt: note.created_at,
3519
+ description: note.body.slice(0, 120),
3520
+ contactName,
3521
+ companyName,
3522
+ stage: deal?.cached_stage ?? null
3523
+ });
3524
+ }
3525
+ for (const deal of dealList) {
3526
+ const rawLog = deal.activity_log;
3527
+ if (!Array.isArray(rawLog) || rawLog.length === 0) continue;
3528
+ const contact = deal.contact ?? null;
3529
+ const contactName = contact ? [contact.first_name, contact.last_name].filter(Boolean).join(" ") || null : null;
3530
+ const companyName = contact?.company?.name ?? null;
3531
+ const activityLog = rawLog;
3532
+ for (let i = 0; i < activityLog.length; i++) {
3533
+ const entry = activityLog[i];
3534
+ const kind = i === 0 ? "deal_created" : "stage_change";
3535
+ let description;
3536
+ if (entry.description) {
3537
+ description = entry.description;
3538
+ } else if (kind === "deal_created") {
3539
+ description = "Deal created";
3540
+ } else {
3541
+ description = entry.title ?? "Stage updated";
3542
+ }
3543
+ entries.push({
3544
+ id: `${kind}-${deal.id}-${i}`,
3545
+ kind,
3546
+ dealId: deal.id,
3547
+ occurredAt: entry.occurredAt,
3548
+ description,
3549
+ contactName,
3550
+ companyName,
3551
+ stage: deal.cached_stage ?? null
3552
+ });
3553
+ }
3554
+ }
3555
+ entries.sort((a, b) => a.occurredAt < b.occurredAt ? 1 : a.occurredAt > b.occurredAt ? -1 : 0);
3556
+ return entries.slice(0, limit);
3557
+ }, [deals, rawNotes, limit]);
3558
+ return {
3559
+ data,
3560
+ isLoading: dealsLoading || notesLoading,
3561
+ error: dealsError ?? notesError ?? null
3562
+ };
3563
+ }
3564
+ var currencyFormatter = new Intl.NumberFormat("en-US", {
3565
+ style: "currency",
3566
+ currency: "USD",
3567
+ maximumFractionDigits: 0
3568
+ });
3569
+ var STAGE_LABELS = {
3570
+ interested: "Interested",
3571
+ booked: "Booked",
3572
+ qualified: "Qualified",
3573
+ demo_booked: "Demo Booked",
3574
+ proposal: "Proposal",
3575
+ proposal_sent: "Proposal Sent",
3576
+ proposal_signed: "Proposal Signed",
3577
+ payment_sent: "Payment Sent",
3578
+ closed_won: "Closed Won",
3579
+ proposal_revision: "Revision",
3580
+ closed_lost: "Closed Lost",
3581
+ nurturing: "Nurturing",
3582
+ cancelled: "Cancelled",
3583
+ no_show: "No Show"
3584
+ };
3585
+ function PipelineFunnelWidget({ onStageClick, getDealValue }) {
3586
+ const { data, isLoading, error } = useCrmPipelineSummary({ getDealValue });
3587
+ if (isLoading) {
3588
+ return /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsx(Center, { p: "xl", children: /* @__PURE__ */ jsx(Loader, {}) }) });
3589
+ }
3590
+ if (error) {
3591
+ return /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsx(CenteredErrorState, { error, title: "Failed to load pipeline data" }) });
3592
+ }
3593
+ const totalDeals = data.reduce((sum, s) => sum + s.count, 0);
3594
+ const maxCount = Math.max(...data.map((s) => s.count), 1);
3595
+ if (totalDeals === 0) {
3596
+ return /* @__PURE__ */ jsxs(Paper, { withBorder: true, p: "md", children: [
3597
+ /* @__PURE__ */ jsx(CardHeader, { icon: /* @__PURE__ */ jsx(IconColumns, { size: 16 }), title: "Pipeline" }),
3598
+ /* @__PURE__ */ jsx(Center, { h: 200, children: /* @__PURE__ */ jsx(Alert, { icon: /* @__PURE__ */ jsx(IconAlertCircle, {}), color: "gray", variant: "light", children: "No deals in the pipeline yet" }) })
3599
+ ] });
3600
+ }
3601
+ return /* @__PURE__ */ jsxs(Paper, { withBorder: true, p: "md", children: [
3602
+ /* @__PURE__ */ jsx(
3603
+ CardHeader,
3604
+ {
3605
+ icon: /* @__PURE__ */ jsx(IconColumns, { size: 16 }),
3606
+ title: "Pipeline",
3607
+ subtitle: `${totalDeals} deal${totalDeals !== 1 ? "s" : ""} total`
3608
+ }
3609
+ ),
3610
+ /* @__PURE__ */ jsx(Box, { children: PIPELINE_FUNNEL_ORDER.map((stage) => {
3611
+ const summary = data.find((s) => s.stage === stage);
3612
+ const isEmpty = summary.count === 0;
3613
+ const barWidth = isEmpty ? 2 : Math.max(4, summary.count / maxCount * 100);
3614
+ return /* @__PURE__ */ jsx(
3615
+ Box,
3616
+ {
3617
+ onClick: () => onStageClick(stage),
3618
+ style: {
3619
+ cursor: "pointer",
3620
+ borderRadius: "var(--mantine-radius-sm)",
3621
+ padding: "6px 8px",
3622
+ transition: `background-color var(--duration-fast) var(--easing)`,
3623
+ marginBottom: 4
3624
+ },
3625
+ onMouseEnter: (e) => {
3626
+ e.currentTarget.style.backgroundColor = "var(--color-surface-hover)";
3627
+ },
3628
+ onMouseLeave: (e) => {
3629
+ e.currentTarget.style.backgroundColor = "transparent";
3630
+ },
3631
+ children: /* @__PURE__ */ jsxs(Group, { gap: "sm", wrap: "nowrap", children: [
3632
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: isEmpty ? "dimmed" : void 0, style: { width: 130, flexShrink: 0 }, children: STAGE_LABELS[stage] }),
3633
+ /* @__PURE__ */ jsx(Badge, { size: "sm", variant: "light", color: isEmpty ? "gray" : void 0, style: { flexShrink: 0 }, children: summary.count }),
3634
+ /* @__PURE__ */ jsx(Badge, { size: "sm", variant: "outline", color: isEmpty ? "gray" : "teal", style: { flexShrink: 0 }, children: currencyFormatter.format(summary.totalValue) }),
3635
+ /* @__PURE__ */ jsx(Box, { style: { flex: 1, minWidth: 0 }, children: /* @__PURE__ */ jsx(
3636
+ Box,
3637
+ {
3638
+ style: {
3639
+ height: 8,
3640
+ width: `${barWidth}%`,
3641
+ borderRadius: 4,
3642
+ backgroundColor: isEmpty ? "var(--color-border)" : "color-mix(in srgb, var(--color-primary) 70%, transparent)",
3643
+ opacity: isEmpty ? 0.4 : 1,
3644
+ transition: `width var(--duration-normal) var(--easing)`
3645
+ }
3646
+ }
3647
+ ) })
3648
+ ] })
3649
+ },
3650
+ stage
3651
+ );
3652
+ }) })
3653
+ ] });
3654
+ }
3655
+ var MAX_VISIBLE = 5;
3656
+ function KindIcon({ kind }) {
3657
+ const size = 16;
3658
+ switch (kind) {
3659
+ case "call":
3660
+ return /* @__PURE__ */ jsx(IconPhone, { size });
3661
+ case "email":
3662
+ return /* @__PURE__ */ jsx(IconMail, { size });
3663
+ case "meeting":
3664
+ return /* @__PURE__ */ jsx(IconCalendar, { size });
3665
+ default:
3666
+ return /* @__PURE__ */ jsx(IconCheckbox, { size });
3667
+ }
3668
+ }
3669
+ function formatDueDate(dueAt) {
3670
+ if (!dueAt) return "No due date";
3671
+ return new Date(dueAt).toLocaleDateString();
3672
+ }
3673
+ function TasksDueWidget({ onTaskClick, onSeeAll }) {
3674
+ const { data: tasks, isLoading, error } = useDealTasksDue({ window: "today_and_overdue" });
3675
+ if (isLoading) {
3676
+ return /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsx(Center, { p: "xl", children: /* @__PURE__ */ jsx(Loader, {}) }) });
3677
+ }
3678
+ if (error) {
3679
+ return /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsx(CenteredErrorState, { error, title: "Failed to load tasks" }) });
3680
+ }
3681
+ const totalCount = tasks?.length ?? 0;
3682
+ const visibleTasks = (tasks ?? []).slice(0, MAX_VISIBLE);
3683
+ const hasMore = totalCount > MAX_VISIBLE;
3684
+ const seeAllLink = onSeeAll && hasMore ? /* @__PURE__ */ jsxs(Anchor, { size: "sm", onClick: onSeeAll, style: { cursor: "pointer" }, children: [
3685
+ "See all (",
3686
+ totalCount,
3687
+ ")"
3688
+ ] }) : void 0;
3689
+ if (totalCount === 0) {
3690
+ return /* @__PURE__ */ jsxs(Paper, { withBorder: true, p: "md", children: [
3691
+ /* @__PURE__ */ jsx(CardHeader, { icon: /* @__PURE__ */ jsx(IconChecklist, { size: 16 }), title: "Tasks Due" }),
3692
+ /* @__PURE__ */ jsx(Center, { h: 120, children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "No tasks due today" }) })
3693
+ ] });
3694
+ }
3695
+ return /* @__PURE__ */ jsxs(Paper, { withBorder: true, p: "md", children: [
3696
+ /* @__PURE__ */ jsx(
3697
+ CardHeader,
3698
+ {
3699
+ icon: /* @__PURE__ */ jsx(IconChecklist, { size: 16 }),
3700
+ title: "Tasks Due",
3701
+ subtitle: `${totalCount} task${totalCount !== 1 ? "s" : ""}`,
3702
+ rightSection: seeAllLink
3703
+ }
3704
+ ),
3705
+ /* @__PURE__ */ jsx(Stack, { gap: "xs", children: visibleTasks.map((task) => /* @__PURE__ */ jsx(
3706
+ Box,
3707
+ {
3708
+ onClick: () => onTaskClick(task.dealId),
3709
+ style: {
3710
+ cursor: "pointer",
3711
+ borderRadius: "var(--mantine-radius-sm)",
3712
+ padding: "6px 8px",
3713
+ transition: `background-color var(--duration-fast) var(--easing)`
3714
+ },
3715
+ onMouseEnter: (e) => {
3716
+ e.currentTarget.style.backgroundColor = "var(--color-surface-hover)";
3717
+ },
3718
+ onMouseLeave: (e) => {
3719
+ e.currentTarget.style.backgroundColor = "transparent";
3720
+ },
3721
+ children: /* @__PURE__ */ jsxs(Group, { gap: "sm", wrap: "nowrap", children: [
3722
+ /* @__PURE__ */ jsx(Text, { c: "dimmed", style: { flexShrink: 0 }, children: /* @__PURE__ */ jsx(KindIcon, { kind: task.kind }) }),
3723
+ /* @__PURE__ */ jsx(Text, { size: "sm", style: { flex: 1, minWidth: 0 }, truncate: true, children: task.title }),
3724
+ /* @__PURE__ */ jsx(Badge, { size: "xs", variant: "light", color: "gray", style: { flexShrink: 0 }, children: formatDueDate(task.dueAt) })
3725
+ ] })
3726
+ },
3727
+ task.id
3728
+ )) })
3729
+ ] });
3730
+ }
3731
+ function ActivityKindIcon({ kind }) {
3732
+ const size = 16;
3733
+ switch (kind) {
3734
+ case "note":
3735
+ return /* @__PURE__ */ jsx(IconNote, { size });
3736
+ case "stage_change":
3737
+ return /* @__PURE__ */ jsx(IconArrowRight, { size });
3738
+ case "deal_created":
3739
+ return /* @__PURE__ */ jsx(IconPlus, { size });
3740
+ }
3741
+ }
3742
+ function formatRelativeTime(occurredAt) {
3743
+ const date = new Date(occurredAt);
3744
+ const now = /* @__PURE__ */ new Date();
3745
+ const diffMs = now.getTime() - date.getTime();
3746
+ const diffMin = Math.floor(diffMs / 6e4);
3747
+ const diffHr = Math.floor(diffMin / 60);
3748
+ const diffDay = Math.floor(diffHr / 24);
3749
+ if (diffMin < 1) return "just now";
3750
+ if (diffMin < 60) return `${diffMin}m ago`;
3751
+ if (diffHr < 24) return `${diffHr}h ago`;
3752
+ if (diffDay < 7) return `${diffDay}d ago`;
3753
+ return date.toLocaleDateString();
3754
+ }
3755
+ function ActivityFeedWidget({ onDealClick, limit }) {
3756
+ const { data, isLoading, error } = useRecentCrmActivity({ limit: limit ?? 15 });
3757
+ if (isLoading) {
3758
+ return /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsx(Center, { p: "xl", children: /* @__PURE__ */ jsx(Loader, {}) }) });
3759
+ }
3760
+ if (error) {
3761
+ return /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsx(CenteredErrorState, { error, title: "Failed to load activity" }) });
3762
+ }
3763
+ if (!data.length) {
3764
+ return /* @__PURE__ */ jsxs(Paper, { withBorder: true, p: "md", children: [
3765
+ /* @__PURE__ */ jsx(CardHeader, { icon: /* @__PURE__ */ jsx(IconHistory, { size: 16 }), title: "Recent Activity" }),
3766
+ /* @__PURE__ */ jsx(Center, { h: 120, children: /* @__PURE__ */ jsx(Alert, { icon: /* @__PURE__ */ jsx(IconAlertCircle, {}), color: "gray", variant: "light", children: "No recent activity" }) })
3767
+ ] });
3768
+ }
3769
+ return /* @__PURE__ */ jsxs(Paper, { withBorder: true, p: "md", children: [
3770
+ /* @__PURE__ */ jsx(CardHeader, { icon: /* @__PURE__ */ jsx(IconHistory, { size: 16 }), title: "Recent Activity" }),
3771
+ /* @__PURE__ */ jsx(Stack, { gap: 4, children: data.map((entry) => {
3772
+ const name = entry.contactName ?? entry.companyName ?? "Unknown";
3773
+ return /* @__PURE__ */ jsx(
3774
+ Box,
3775
+ {
3776
+ onClick: () => onDealClick(entry.dealId),
3777
+ style: {
3778
+ cursor: "pointer",
3779
+ borderRadius: "var(--mantine-radius-sm)",
3780
+ padding: "6px 8px",
3781
+ transition: `background-color var(--duration-fast) var(--easing)`
3782
+ },
3783
+ onMouseEnter: (e) => {
3784
+ e.currentTarget.style.backgroundColor = "var(--color-surface-hover)";
3785
+ },
3786
+ onMouseLeave: (e) => {
3787
+ e.currentTarget.style.backgroundColor = "transparent";
3788
+ },
3789
+ children: /* @__PURE__ */ jsxs(Group, { gap: "sm", wrap: "nowrap", align: "flex-start", children: [
3790
+ /* @__PURE__ */ jsx(Text, { c: "dimmed", style: { flexShrink: 0, paddingTop: 2 }, children: /* @__PURE__ */ jsx(ActivityKindIcon, { kind: entry.kind }) }),
3791
+ /* @__PURE__ */ jsx(Box, { style: { flex: 1, minWidth: 0 }, children: /* @__PURE__ */ jsxs(Group, { gap: "xs", wrap: "nowrap", children: [
3792
+ /* @__PURE__ */ jsx(Text, { size: "sm", fw: 600, truncate: true, style: { flexShrink: 0, maxWidth: 140 }, children: name }),
3793
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", truncate: true, style: { flex: 1, minWidth: 0 }, children: entry.description })
3794
+ ] }) }),
3795
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", style: { flexShrink: 0, whiteSpace: "nowrap" }, children: formatRelativeTime(entry.occurredAt) })
3796
+ ] })
3797
+ },
3798
+ entry.id
3799
+ );
3800
+ }) })
3801
+ ] });
3802
+ }
3803
+ var currencyFormatter2 = new Intl.NumberFormat("en-US", {
3804
+ style: "currency",
3805
+ currency: "USD",
3806
+ maximumFractionDigits: 0
3807
+ });
3808
+ function formatPercent(value) {
3809
+ return `${Math.round(value * 100)}%`;
3810
+ }
3811
+ function StatTile({ label, value }) {
3812
+ return /* @__PURE__ */ jsx(Card, { padding: "sm", children: /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
3813
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: label }),
3814
+ /* @__PURE__ */ jsx(Text, { fw: 700, size: "xl", children: value })
3815
+ ] }) });
3816
+ }
3817
+ function MetricsStrip() {
3818
+ const { data } = useCrmQuickMetrics();
3819
+ return /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsxs(SimpleGrid, { cols: { base: 2, sm: 4 }, children: [
3820
+ /* @__PURE__ */ jsx(StatTile, { label: "Total Pipeline Value", value: currencyFormatter2.format(data.totalPipelineValue) }),
3821
+ /* @__PURE__ */ jsx(StatTile, { label: "Win Rate", value: formatPercent(data.winRate) }),
3822
+ /* @__PURE__ */ jsx(StatTile, { label: "Open Deals", value: String(data.openDeals) }),
3823
+ /* @__PURE__ */ jsx(StatTile, { label: "Won This Period", value: String(data.wonDeals) })
3824
+ ] }) });
3825
+ }
3826
+ function CrmOverview({
3827
+ onStageClick,
3828
+ onDealClick,
3829
+ onGoToPipeline,
3830
+ getDealValue,
3831
+ renderActions
3832
+ }) {
3833
+ const rightSection = renderActions ? renderActions() : /* @__PURE__ */ jsx(Button, { leftSection: /* @__PURE__ */ jsx(IconColumns, { size: 16 }), variant: "light", size: "sm", onClick: onGoToPipeline, children: "Go to Pipeline" });
3834
+ return /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
3835
+ /* @__PURE__ */ jsx(
3836
+ PageTitleCaption,
3837
+ {
3838
+ title: "CRM Overview",
3839
+ caption: "Pipeline health, tasks, and recent activity at a glance.",
3840
+ rightSection
3841
+ }
3842
+ ),
3843
+ /* @__PURE__ */ jsx(MetricsStrip, {}),
3844
+ /* @__PURE__ */ jsxs(SimpleGrid, { cols: { base: 1, lg: 2 }, spacing: "md", children: [
3845
+ /* @__PURE__ */ jsx(PipelineFunnelWidget, { onStageClick, getDealValue }),
3846
+ /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
3847
+ /* @__PURE__ */ jsx(TasksDueWidget, { onTaskClick: onDealClick }),
3848
+ /* @__PURE__ */ jsx(ActivityFeedWidget, { onDealClick })
3849
+ ] })
3850
+ ] })
3851
+ ] });
3852
+ }
3853
+
3854
+ // src/features/acquisition/workbench/constants.ts
3855
+ var SAVED_VIEW_PRESETS = [
3856
+ {
3857
+ id: "my-deals",
3858
+ label: "My Deals",
3859
+ iconName: "IconUser",
3860
+ target: "/crm/deals"
3861
+ },
3862
+ {
3863
+ id: "overdue",
3864
+ label: "Overdue",
3865
+ iconName: "IconClockExclamation",
3866
+ target: "/crm/deals"
3867
+ },
3868
+ {
3869
+ id: "won-this-month",
3870
+ label: "Won this month",
3871
+ iconName: "IconTrophy",
3872
+ target: "/crm/deals",
3873
+ urlFilters: { stage: "closed_won" },
3874
+ predicate: (deal) => {
3875
+ if (deal.cached_stage !== "closed_won") return false;
3876
+ const updated = new Date(deal.updated_at);
3877
+ const now = /* @__PURE__ */ new Date();
3878
+ return updated.getMonth() === now.getMonth() && updated.getFullYear() === now.getFullYear();
3879
+ }
3880
+ }
3881
+ ];
3882
+ var KIND_ICONS = {
3883
+ call: IconPhone,
3884
+ email: IconMail,
3885
+ meeting: IconCalendar,
3886
+ other: IconCheckbox
3887
+ };
3888
+ function formatDueLabel(dueAt) {
3889
+ if (!dueAt) return "";
3890
+ const date = new Date(dueAt);
3891
+ const now = /* @__PURE__ */ new Date();
3892
+ const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
3893
+ const dueDay = new Date(date.getFullYear(), date.getMonth(), date.getDate());
3894
+ const diffDays = Math.round((dueDay.getTime() - today.getTime()) / 864e5);
3895
+ if (diffDays < 0) return `${Math.abs(diffDays)}d overdue`;
3896
+ if (diffDays === 0) {
3897
+ return date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
3898
+ }
3899
+ return `in ${diffDays}d`;
3900
+ }
3901
+ function TaskRow({ task, onClick }) {
3902
+ const KindIcon2 = KIND_ICONS[task.kind];
3903
+ const dueLabel = formatDueLabel(task.dueAt);
3904
+ const isOverdue = task.dueAt !== null && new Date(task.dueAt) < /* @__PURE__ */ new Date();
3905
+ return /* @__PURE__ */ jsx(
3906
+ UnstyledButton,
3907
+ {
3908
+ onClick,
3909
+ style: {
3910
+ display: "block",
3911
+ width: "100%",
3912
+ padding: "4px 6px",
3913
+ borderRadius: "var(--mantine-radius-sm)",
3914
+ transition: `background-color var(--duration-fast) var(--easing)`
3915
+ },
3916
+ onMouseEnter: (e) => {
3917
+ e.currentTarget.style.backgroundColor = "var(--color-surface-hover)";
3918
+ },
3919
+ onMouseLeave: (e) => {
3920
+ e.currentTarget.style.backgroundColor = "transparent";
3921
+ },
3922
+ children: /* @__PURE__ */ jsxs(Group, { gap: "xs", wrap: "nowrap", children: [
3923
+ /* @__PURE__ */ jsx(KindIcon2, { size: 14, style: { color: "var(--color-text-dimmed)", flexShrink: 0 } }),
3924
+ /* @__PURE__ */ jsx(Text, { size: "xs", truncate: true, style: { flex: 1, minWidth: 0 }, children: task.title }),
3925
+ dueLabel && /* @__PURE__ */ jsx(Badge, { size: "xs", variant: "light", color: isOverdue ? "red" : "gray", style: { flexShrink: 0 }, children: dueLabel })
3926
+ ] })
3927
+ }
3928
+ );
3929
+ }
3930
+ function MyTasksPanel({ onTaskClick, onSeeAll }) {
3931
+ const { data, isLoading, isError } = useDealTasksDue({ window: "today_and_overdue" });
3932
+ const tasks = data ?? [];
3933
+ const total = tasks.length;
3934
+ const visible = tasks.slice(0, 5);
3935
+ return /* @__PURE__ */ jsxs(Box, { children: [
3936
+ /* @__PURE__ */ jsx(Divider, { mb: "xs" }),
3937
+ /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
3938
+ /* @__PURE__ */ jsxs(Group, { justify: "space-between", mb: 4, children: [
3939
+ /* @__PURE__ */ jsx(Text, { fz: "xs", tt: "uppercase", c: "dimmed", fw: 600, children: "My Tasks" }),
3940
+ total > 0 && /* @__PURE__ */ jsx(Badge, { size: "xs", variant: "light", children: total })
3941
+ ] }),
3942
+ isLoading && /* @__PURE__ */ jsx(Center, { py: 4, children: /* @__PURE__ */ jsx(Loader, { size: "xs" }) }),
3943
+ isError && /* @__PURE__ */ jsx(Text, { size: "xs", c: "red", children: "Failed to load tasks" }),
3944
+ !isLoading && !isError && visible.length === 0 && /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "No tasks due \u2014 you're caught up." }),
3945
+ !isLoading && !isError && visible.map((task) => /* @__PURE__ */ jsx(TaskRow, { task, onClick: () => onTaskClick(task.dealId) }, task.id)),
3946
+ 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: [
3947
+ "See all (",
3948
+ total,
3949
+ ")"
3950
+ ] }) })
3951
+ ] })
3952
+ ] });
3953
+ }
3954
+ var ICON_MAP = {
3955
+ IconUser,
3956
+ IconClockExclamation,
3957
+ IconTrophy
3958
+ };
3959
+ function SavedViewsPanel({ onViewClick }) {
3960
+ return /* @__PURE__ */ jsxs(Box, { children: [
3961
+ /* @__PURE__ */ jsx(Divider, { mb: "xs" }),
3962
+ /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
3963
+ /* @__PURE__ */ jsx(Text, { fz: "xs", tt: "uppercase", c: "dimmed", fw: 600, mb: 4, children: "Saved Views" }),
3964
+ SAVED_VIEW_PRESETS.map((preset) => {
3965
+ const Icon = ICON_MAP[preset.iconName];
3966
+ return /* @__PURE__ */ jsx(
3967
+ UnstyledButton,
3968
+ {
3969
+ onClick: () => onViewClick(preset),
3970
+ style: {
3971
+ display: "block",
3972
+ width: "100%",
3973
+ padding: "4px 6px",
3974
+ borderRadius: "var(--mantine-radius-sm)",
3975
+ transition: `background-color var(--duration-fast) var(--easing)`
3976
+ },
3977
+ onMouseEnter: (e) => {
3978
+ e.currentTarget.style.backgroundColor = "var(--color-surface-hover)";
3979
+ },
3980
+ onMouseLeave: (e) => {
3981
+ e.currentTarget.style.backgroundColor = "transparent";
3982
+ },
3983
+ children: /* @__PURE__ */ jsxs(Group, { gap: "xs", wrap: "nowrap", children: [
3984
+ /* @__PURE__ */ jsx(Icon, { size: 14, style: { color: "var(--color-text-dimmed)", flexShrink: 0 } }),
3985
+ /* @__PURE__ */ jsx(Text, { size: "xs", children: preset.label })
3986
+ ] })
3987
+ },
3988
+ preset.id
3989
+ );
3990
+ })
3991
+ ] })
3992
+ ] });
3993
+ }
3994
+ var KIND_OPTIONS = [
3995
+ { value: "call", label: "Call" },
3996
+ { value: "email", label: "Email" },
3997
+ { value: "meeting", label: "Meeting" },
3998
+ { value: "other", label: "Other" }
3999
+ ];
4000
+ function buildDealLabel(deal) {
4001
+ const contact = deal.contact;
4002
+ if (!contact) return `Deal ${deal.id.slice(0, 8)}`;
4003
+ const name = `${contact.first_name ?? ""} ${contact.last_name ?? ""}`.trim();
4004
+ const company = contact.company?.name ?? "\u2014";
4005
+ return name ? `${name} \u2013 ${company}` : `Deal ${deal.id.slice(0, 8)}`;
4006
+ }
4007
+ function QuickCreateActions({ onNewDeal }) {
4008
+ const [open, setOpen] = useState(false);
4009
+ const [dealId, setDealId] = useState(null);
4010
+ const [title, setTitle] = useState("");
4011
+ const [description, setDescription] = useState("");
4012
+ const [kind, setKind] = useState("other");
4013
+ const [dueAt, setDueAt] = useState("");
4014
+ const { data: deals } = useDeals();
4015
+ const createTask = useCreateDealTask();
4016
+ const dealOptions = (deals ?? []).map((deal) => ({
4017
+ value: deal.id,
4018
+ label: buildDealLabel(deal)
4019
+ }));
4020
+ function resetForm() {
4021
+ setDealId(null);
4022
+ setTitle("");
4023
+ setDescription("");
4024
+ setKind("other");
4025
+ setDueAt("");
4026
+ }
4027
+ function handleClose() {
4028
+ setOpen(false);
4029
+ resetForm();
4030
+ }
4031
+ async function handleSubmit() {
4032
+ if (!dealId || !title) return;
4033
+ await createTask.mutateAsync({
4034
+ dealId,
4035
+ title,
4036
+ description: description || null,
4037
+ kind,
4038
+ dueAt: dueAt ? new Date(dueAt).toISOString() : null
4039
+ });
4040
+ handleClose();
4041
+ }
4042
+ const isSubmitDisabled = !dealId || !title || createTask.isPending;
4043
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
4044
+ /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
4045
+ /* @__PURE__ */ jsx(Text, { fz: "xs", tt: "uppercase", c: "dimmed", fw: 600, children: "QUICK CREATE" }),
4046
+ /* @__PURE__ */ jsxs(Stack, { gap: "xs", children: [
4047
+ /* @__PURE__ */ jsx(Button, { variant: "light", size: "xs", leftSection: /* @__PURE__ */ jsx(IconPlus, { size: 14 }), fullWidth: true, onClick: onNewDeal, children: "New Deal" }),
4048
+ /* @__PURE__ */ jsx(
4049
+ Button,
4050
+ {
4051
+ variant: "light",
4052
+ size: "xs",
4053
+ leftSection: /* @__PURE__ */ jsx(IconPlus, { size: 14 }),
4054
+ fullWidth: true,
4055
+ onClick: () => setOpen(true),
4056
+ children: "New Task"
4057
+ }
4058
+ )
4059
+ ] })
4060
+ ] }),
4061
+ /* @__PURE__ */ jsxs(Modal, { opened: open, onClose: handleClose, title: "New Task", size: "md", children: [
4062
+ /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
4063
+ /* @__PURE__ */ jsx(
4064
+ Select,
4065
+ {
4066
+ label: "Deal",
4067
+ placeholder: "Select a deal",
4068
+ data: dealOptions,
4069
+ value: dealId,
4070
+ onChange: setDealId,
4071
+ searchable: true,
4072
+ required: true
4073
+ }
4074
+ ),
4075
+ /* @__PURE__ */ jsx(
4076
+ TextInput,
4077
+ {
4078
+ label: "Title",
4079
+ placeholder: "Task title",
4080
+ value: title,
4081
+ onChange: (e) => setTitle(e.currentTarget.value),
4082
+ required: true
4083
+ }
4084
+ ),
4085
+ /* @__PURE__ */ jsx(
4086
+ Textarea,
4087
+ {
4088
+ label: "Description",
4089
+ placeholder: "Optional description",
4090
+ value: description,
4091
+ onChange: (e) => setDescription(e.currentTarget.value),
4092
+ autosize: true,
4093
+ minRows: 2,
4094
+ maxRows: 5
4095
+ }
4096
+ ),
4097
+ /* @__PURE__ */ jsx(
4098
+ Select,
4099
+ {
4100
+ label: "Kind",
4101
+ data: KIND_OPTIONS,
4102
+ value: kind,
4103
+ onChange: (v) => setKind(v ?? "other")
4104
+ }
4105
+ ),
4106
+ /* @__PURE__ */ jsx(
4107
+ TextInput,
4108
+ {
4109
+ type: "datetime-local",
4110
+ label: "Due At",
4111
+ value: dueAt,
4112
+ onChange: (e) => setDueAt(e.currentTarget.value)
4113
+ }
4114
+ )
4115
+ ] }),
4116
+ /* @__PURE__ */ jsxs(Group, { justify: "flex-end", mt: "md", children: [
4117
+ /* @__PURE__ */ jsx(Button, { variant: "default", onClick: handleClose, children: "Cancel" }),
4118
+ /* @__PURE__ */ jsx(Button, { onClick: handleSubmit, loading: createTask.isPending, disabled: isSubmitDisabled, children: "Create" })
4119
+ ] })
4120
+ ] })
4121
+ ] });
4122
+ }
4123
+ function ringColor(completed, total) {
4124
+ if (total === 0) return "gray";
4125
+ const pct = completed / total;
4126
+ if (pct > 0.75) return "green";
4127
+ if (pct > 0.5) return "yellow";
4128
+ return "gray";
4129
+ }
4130
+ function ringValue(completed, total) {
4131
+ if (total === 0) return 0;
4132
+ return Math.round(completed / total * 100);
4133
+ }
4134
+ function formatDate2(iso) {
4135
+ return new Date(iso).toLocaleDateString();
4136
+ }
4137
+ function daysRelative(targetEndDate) {
4138
+ const now = /* @__PURE__ */ new Date();
4139
+ const end = new Date(targetEndDate);
4140
+ const diffMs = end.getTime() - now.getTime();
4141
+ const diffDays = Math.ceil(diffMs / 864e5);
4142
+ if (diffDays >= 0) {
4143
+ return { label: `${diffDays} day${diffDays === 1 ? "" : "s"} remaining`, overdue: false };
4144
+ }
4145
+ return { label: `${Math.abs(diffDays)} day${Math.abs(diffDays) === 1 ? "" : "s"} overdue`, overdue: true };
4146
+ }
4147
+ function HealthStatusCard({
4148
+ status,
4149
+ milestoneCount,
4150
+ completedMilestones,
4151
+ taskCount,
4152
+ completedTasks,
4153
+ startDate,
4154
+ targetEndDate
4155
+ }) {
4156
+ const milestoneRingValue = ringValue(completedMilestones, milestoneCount);
4157
+ const taskRingValue = ringValue(completedTasks, taskCount);
4158
+ const timeInfo = targetEndDate ? daysRelative(targetEndDate) : null;
4159
+ return /* @__PURE__ */ jsxs(SimpleGrid, { cols: 4, children: [
4160
+ /* @__PURE__ */ jsx(Card, { withBorder: true, radius: "md", p: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: 8, children: [
4161
+ /* @__PURE__ */ jsxs(Group, { gap: 6, children: [
4162
+ /* @__PURE__ */ jsx(ThemeIcon, { size: 20, variant: "light", color: "gray", children: /* @__PURE__ */ jsx(IconHeartbeat, { size: 14 }) }),
4163
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", fw: 500, children: "Status" })
4164
+ ] }),
4165
+ /* @__PURE__ */ jsx(StatusBadge, { status, size: "md" })
4166
+ ] }) }),
4167
+ /* @__PURE__ */ jsx(Card, { withBorder: true, radius: "md", p: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: 8, children: [
4168
+ /* @__PURE__ */ jsxs(Group, { gap: 6, children: [
4169
+ /* @__PURE__ */ jsx(ThemeIcon, { size: 20, variant: "light", color: "gray", children: /* @__PURE__ */ jsx(IconFlag, { size: 14 }) }),
4170
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", fw: 500, children: "Milestones" })
4171
+ ] }),
4172
+ /* @__PURE__ */ jsxs(Group, { gap: 10, align: "center", children: [
4173
+ /* @__PURE__ */ jsx(
4174
+ RingProgress,
4175
+ {
4176
+ size: 48,
4177
+ thickness: 5,
4178
+ sections: [{ value: milestoneRingValue, color: ringColor(completedMilestones, milestoneCount) }]
4179
+ }
4180
+ ),
4181
+ /* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
4182
+ /* @__PURE__ */ jsxs(Text, { size: "sm", fw: 600, children: [
4183
+ completedMilestones,
4184
+ "/",
4185
+ milestoneCount
4186
+ ] }),
4187
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "complete" })
4188
+ ] })
4189
+ ] })
4190
+ ] }) }),
4191
+ /* @__PURE__ */ jsx(Card, { withBorder: true, radius: "md", p: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: 8, children: [
4192
+ /* @__PURE__ */ jsxs(Group, { gap: 6, children: [
4193
+ /* @__PURE__ */ jsx(ThemeIcon, { size: 20, variant: "light", color: "gray", children: /* @__PURE__ */ jsx(IconFileText, { size: 14 }) }),
4194
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", fw: 500, children: "Tasks" })
4195
+ ] }),
4196
+ /* @__PURE__ */ jsxs(Group, { gap: 10, align: "center", children: [
4197
+ /* @__PURE__ */ jsx(
4198
+ RingProgress,
4199
+ {
4200
+ size: 48,
4201
+ thickness: 5,
4202
+ sections: [{ value: taskRingValue, color: ringColor(completedTasks, taskCount) }]
4203
+ }
4204
+ ),
4205
+ /* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
4206
+ /* @__PURE__ */ jsxs(Text, { size: "sm", fw: 600, children: [
4207
+ completedTasks,
4208
+ "/",
4209
+ taskCount
4210
+ ] }),
4211
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "approved" })
4212
+ ] })
4213
+ ] })
4214
+ ] }) }),
4215
+ /* @__PURE__ */ jsx(Card, { withBorder: true, radius: "md", p: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: 8, children: [
4216
+ /* @__PURE__ */ jsxs(Group, { gap: 6, children: [
4217
+ /* @__PURE__ */ jsx(ThemeIcon, { size: 20, variant: "light", color: "gray", children: /* @__PURE__ */ jsx(IconCalendar, { size: 14 }) }),
4218
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", fw: 500, children: "Timeline" })
4219
+ ] }),
4220
+ /* @__PURE__ */ jsx(Stack, { gap: 2, children: startDate && targetEndDate ? /* @__PURE__ */ jsxs(Fragment, { children: [
4221
+ /* @__PURE__ */ jsxs(Text, { size: "sm", fw: 500, children: [
4222
+ formatDate2(startDate),
4223
+ " \u2192 ",
4224
+ formatDate2(targetEndDate)
4225
+ ] }),
4226
+ timeInfo && /* @__PURE__ */ jsx(Text, { size: "xs", c: timeInfo.overdue ? "red" : "dimmed", children: timeInfo.label })
4227
+ ] }) : startDate ? /* @__PURE__ */ jsxs(Text, { size: "sm", fw: 500, children: [
4228
+ "Started ",
4229
+ formatDate2(startDate)
4230
+ ] }) : targetEndDate ? /* @__PURE__ */ jsxs(Text, { size: "sm", fw: 500, children: [
4231
+ "Due ",
4232
+ formatDate2(targetEndDate)
4233
+ ] }) : /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "No dates set" }) })
4234
+ ] }) })
4235
+ ] });
4236
+ }
4237
+
4238
+ // src/features/delivery/_shared.ts
4239
+ var projectStatusColors = {
4240
+ active: "blue",
4241
+ on_track: "green",
4242
+ at_risk: "yellow",
4243
+ blocked: "red",
4244
+ completed: "teal",
4245
+ paused: "gray"
4246
+ };
4247
+ var milestoneStatusColors = {
4248
+ upcoming: "gray",
4249
+ in_progress: "blue",
4250
+ completed: "green",
4251
+ overdue: "red",
4252
+ blocked: "orange"
4253
+ };
4254
+ var taskStatusColors = {
4255
+ pending: "gray",
4256
+ planned: "gray",
4257
+ in_progress: "blue",
4258
+ submitted: "yellow",
4259
+ approved: "green",
4260
+ rejected: "red",
4261
+ revision_requested: "orange"
4262
+ };
4263
+ var taskTypeColors = {
4264
+ documentation: "blue",
4265
+ code: "violet",
4266
+ report: "cyan",
4267
+ design: "pink",
4268
+ other: "gray"
4269
+ };
4270
+ var noteTypeColors = {
4271
+ call_note: "blue",
4272
+ status_update: "green",
4273
+ issue: "yellow",
4274
+ blocker: "red"
4275
+ };
4276
+ function formatStatusLabel(status) {
4277
+ return status.split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
4278
+ }
4279
+ function formatDate3(dateString) {
4280
+ if (!dateString) return "-";
4281
+ return new Date(dateString).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" });
4282
+ }
4283
+ function calculateProgress(completed, total) {
4284
+ if (total === 0) return 0;
4285
+ return Math.round(completed / total * 100);
4286
+ }
4287
+ var MILESTONE_ICONS = {
4288
+ upcoming: IconClock,
4289
+ in_progress: IconFlag,
4290
+ completed: IconCircleCheck,
4291
+ overdue: IconAlertTriangle,
4292
+ blocked: IconLock
4293
+ };
4294
+ function MilestoneTimeline({ milestones, tasks }) {
4295
+ const sorted = [...milestones].sort((a, b) => a.sequence - b.sequence);
4296
+ const defaultExpanded = new Set(sorted.filter((m) => m.status === "in_progress").map((m) => m.id));
4297
+ const [expanded, setExpanded] = useState(defaultExpanded);
4298
+ if (sorted.length === 0) {
4299
+ return /* @__PURE__ */ jsx(EmptyState, { icon: IconInbox, title: "No milestones yet" });
4300
+ }
4301
+ function toggleMilestone(id) {
4302
+ setExpanded((prev) => {
4303
+ const next = new Set(prev);
4304
+ if (next.has(id)) {
4305
+ next.delete(id);
4306
+ } else {
4307
+ next.add(id);
4308
+ }
4309
+ return next;
4310
+ });
4311
+ }
4312
+ const activeIndex = sorted.reduce((last, m, i) => m.status !== "upcoming" ? i : last, -1);
4313
+ 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) => {
4314
+ const Icon = MILESTONE_ICONS[milestone.status] || IconFlag;
4315
+ const color = milestoneStatusColors[milestone.status] || "gray";
4316
+ const isExpanded = expanded.has(milestone.id);
4317
+ const milestoneTasks = tasks.filter((t) => t.milestone_id === milestone.id);
4318
+ const ChevronIcon = isExpanded ? IconChevronDown : IconChevronRight;
4319
+ return /* @__PURE__ */ jsx(
4320
+ Timeline.Item,
4321
+ {
4322
+ bullet: /* @__PURE__ */ jsx(ThemeIcon, { size: 28, variant: "filled", color, radius: "xl", children: /* @__PURE__ */ jsx(Icon, { size: 14 }) }),
4323
+ children: /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
4324
+ /* @__PURE__ */ jsxs(
4325
+ Group,
4326
+ {
4327
+ gap: 8,
4328
+ style: { cursor: "pointer", userSelect: "none" },
4329
+ onClick: () => toggleMilestone(milestone.id),
4330
+ children: [
4331
+ /* @__PURE__ */ jsx(Text, { size: "sm", fw: 700, children: milestone.name }),
4332
+ /* @__PURE__ */ jsx(Badge, { size: "sm", variant: "light", color, children: formatStatusLabel(milestone.status) }),
4333
+ /* @__PURE__ */ jsx(ThemeIcon, { size: 16, variant: "transparent", color: "dimmed", children: /* @__PURE__ */ jsx(ChevronIcon, { size: 14 }) })
4334
+ ]
4335
+ }
4336
+ ),
4337
+ milestone.due_date && /* @__PURE__ */ jsxs(Text, { size: "xs", c: "dimmed", children: [
4338
+ "Due ",
4339
+ new Date(milestone.due_date).toLocaleDateString()
4340
+ ] }),
4341
+ /* @__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: [
4342
+ /* @__PURE__ */ jsxs(Group, { gap: 6, wrap: "nowrap", children: [
4343
+ /* @__PURE__ */ jsx(Text, { size: "xs", fw: 500, style: { flex: 1, minWidth: 0 }, children: task.name }),
4344
+ /* @__PURE__ */ jsx(Badge, { size: "xs", variant: "light", color: taskTypeColors[task.type] || "gray", children: formatStatusLabel(task.type) }),
4345
+ /* @__PURE__ */ jsx(Badge, { size: "xs", variant: "light", color: taskStatusColors[task.status] || "gray", children: formatStatusLabel(task.status) })
4346
+ ] }),
4347
+ task.due_date && /* @__PURE__ */ jsxs(Text, { size: "xs", c: "dimmed", mt: 4, children: [
4348
+ "Due ",
4349
+ new Date(task.due_date).toLocaleDateString()
4350
+ ] })
4351
+ ] }, task.id)) }) })
4352
+ ] })
4353
+ },
4354
+ milestone.id
4355
+ );
4356
+ }) }) });
4357
+ }
4358
+ function TaskCard({ task }) {
4359
+ return /* @__PURE__ */ jsx(Card, { withBorder: true, radius: "sm", p: "sm", children: /* @__PURE__ */ jsxs(Stack, { gap: 6, children: [
4360
+ /* @__PURE__ */ jsxs(Group, { justify: "space-between", wrap: "nowrap", children: [
4361
+ /* @__PURE__ */ jsxs(Group, { gap: 6, wrap: "nowrap", style: { minWidth: 0 }, children: [
4362
+ /* @__PURE__ */ jsx(Text, { fw: 500, size: "sm", style: { flexShrink: 0 }, children: task.name }),
4363
+ /* @__PURE__ */ jsx(Badge, { size: "xs", variant: "light", color: taskTypeColors[task.type] || "gray", children: formatStatusLabel(task.type) }),
4364
+ /* @__PURE__ */ jsx(StatusBadge, { status: task.status, size: "xs" })
4365
+ ] }),
4366
+ task.file_url && /* @__PURE__ */ jsx(
4367
+ ActionIcon,
4368
+ {
4369
+ component: "a",
4370
+ href: task.file_url,
4371
+ target: "_blank",
4372
+ rel: "noopener noreferrer",
4373
+ variant: "light",
4374
+ size: "sm",
4375
+ "aria-label": "Download file",
4376
+ children: /* @__PURE__ */ jsx(IconDownload, { size: 14 })
4377
+ }
4378
+ )
4379
+ ] }),
4380
+ task.description && /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: task.description }),
4381
+ task.due_date && /* @__PURE__ */ jsxs(Text, { size: "xs", c: "dimmed", children: [
4382
+ "Due ",
4383
+ new Date(task.due_date).toLocaleDateString()
4384
+ ] })
4385
+ ] }) });
4386
+ }
4387
+ var ProjectsSidebarTop = () => {
4388
+ return /* @__PURE__ */ jsx(SubshellSidebarSection, { icon: IconBriefcase, label: "Projects" });
4389
+ };
4390
+ var PROJECT_ITEMS = [{ label: "Projects", to: "/projects", icon: IconBriefcase, exact: true }];
4391
+ var WORK_ITEMS = [
4392
+ { label: "Tasks", to: "/projects/tasks", icon: IconChecklist, exact: false },
4393
+ { label: "Milestones", to: "/projects/milestones", icon: IconFlag, exact: false }
4394
+ ];
4395
+ var COMMUNICATION_ITEMS = [{ label: "Notes", to: "/projects/notes", icon: IconNotes, exact: false }];
4396
+ var ProjectsSidebarMiddle = ({ currentPath, onNavigate }) => {
4397
+ const renderItems = (items) => items.map((item) => {
4398
+ const isActive = item.exact ? currentPath === item.to || currentPath === `${item.to}/` : currentPath.startsWith(item.to);
4399
+ return /* @__PURE__ */ jsx(
4400
+ SidebarListItem,
4401
+ {
4402
+ icon: item.icon,
4403
+ label: item.label,
4404
+ isActive,
4405
+ onClick: () => onNavigate(item.to)
4406
+ },
4407
+ item.to
4408
+ );
4409
+ });
4410
+ return /* @__PURE__ */ jsxs(Stack, { gap: 0, style: { flex: 1, overflowY: "auto" }, children: [
4411
+ /* @__PURE__ */ jsx(Stack, { gap: 0, p: "sm", children: renderItems(PROJECT_ITEMS) }),
4412
+ /* @__PURE__ */ jsxs(Fragment, { children: [
4413
+ /* @__PURE__ */ jsx(SubshellSidebarSection, { icon: IconListCheck, label: "Work", withTopBorder: true }),
4414
+ /* @__PURE__ */ jsx(Stack, { gap: 0, p: "sm", children: renderItems(WORK_ITEMS) })
4415
+ ] }),
4416
+ /* @__PURE__ */ jsxs(Fragment, { children: [
4417
+ /* @__PURE__ */ jsx(SubshellSidebarSection, { icon: IconMessageCircle, label: "Communication", withTopBorder: true }),
4418
+ /* @__PURE__ */ jsx(Stack, { gap: 0, p: "sm", children: renderItems(COMMUNICATION_ITEMS) })
4419
+ ] })
4420
+ ] });
4421
+ };
4422
+ var ProjectsSidebar = ({ currentPath, onNavigate }) => {
4423
+ return /* @__PURE__ */ jsxs(Stack, { gap: 0, style: { height: "100%", display: "flex", flexDirection: "column" }, children: [
4424
+ /* @__PURE__ */ jsx(ProjectsSidebarTop, {}),
4425
+ /* @__PURE__ */ jsx(ProjectsSidebarMiddle, { currentPath, onNavigate })
4426
+ ] });
4427
+ };
4428
+ var taskStatusOptions = [
4429
+ { value: "planned", label: "Planned" },
4430
+ { value: "in_progress", label: "In Progress" },
4431
+ { value: "blocked", label: "Blocked" },
4432
+ { value: "completed", label: "Completed" },
4433
+ { value: "cancelled", label: "Cancelled" },
4434
+ { value: "submitted", label: "Submitted" },
4435
+ { value: "approved", label: "Approved" },
4436
+ { value: "rejected", label: "Rejected" },
4437
+ { value: "revision_requested", label: "Revision Requested" }
4438
+ ];
4439
+ var taskTypeOptions = [
4440
+ { value: "documentation", label: "Documentation" },
4441
+ { value: "code", label: "Code" },
4442
+ { value: "report", label: "Report" },
4443
+ { value: "design", label: "Design" },
4444
+ { value: "other", label: "Other" }
4445
+ ];
4446
+ function AllTasksPage() {
4447
+ const [statusFilter, setStatusFilter] = useState(null);
4448
+ const [typeFilter, setTypeFilter] = useState(null);
4449
+ const { data: tasks, isLoading } = useTasks({
4450
+ status: statusFilter || void 0,
4451
+ type: typeFilter || void 0
4452
+ });
4453
+ const { data: projects } = useProjects();
4454
+ const projectNameById = useMemo(() => new Map(projects?.map((p) => [p.id, p.name]) ?? []), [projects]);
4455
+ return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsxs(PageContainer, { children: [
4456
+ /* @__PURE__ */ jsx(Stack, { children: /* @__PURE__ */ jsx(PageTitleCaption, { title: "All Tasks", caption: "Tasks across all projects, filterable by status and type" }) }),
4457
+ /* @__PURE__ */ jsx(Paper, { children: /* @__PURE__ */ jsxs(Stack, { children: [
4458
+ /* @__PURE__ */ jsxs(Group, { m: "sm", children: [
4459
+ /* @__PURE__ */ jsx(
4460
+ Select,
4461
+ {
4462
+ placeholder: "All Statuses",
4463
+ data: taskStatusOptions,
4464
+ style: { minWidth: 180, maxWidth: 220 },
4465
+ size: "sm",
4466
+ clearable: true,
4467
+ value: statusFilter,
4468
+ onChange: setStatusFilter
4469
+ }
4470
+ ),
4471
+ /* @__PURE__ */ jsx(
4472
+ Select,
4473
+ {
4474
+ placeholder: "All Types",
4475
+ data: taskTypeOptions,
4476
+ style: { minWidth: 180, maxWidth: 220 },
4477
+ size: "sm",
4478
+ clearable: true,
4479
+ value: typeFilter,
4480
+ onChange: setTypeFilter
4481
+ }
4482
+ )
4483
+ ] }),
4484
+ isLoading ? /* @__PURE__ */ jsx(Center, { p: "xl", children: /* @__PURE__ */ jsx(Loader, {}) }) : !tasks?.length ? /* @__PURE__ */ jsx(EmptyState, { icon: IconChecklist, title: "No tasks found" }) : /* @__PURE__ */ jsxs(Table, { children: [
4485
+ /* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
4486
+ /* @__PURE__ */ jsx(Table.Th, { children: "Status" }),
4487
+ /* @__PURE__ */ jsx(Table.Th, { children: "Type" }),
4488
+ /* @__PURE__ */ jsx(Table.Th, { children: "Name" }),
4489
+ /* @__PURE__ */ jsx(Table.Th, { children: "Project" }),
4490
+ /* @__PURE__ */ jsx(Table.Th, { children: "Milestone" }),
4491
+ /* @__PURE__ */ jsx(Table.Th, { children: "Created" })
4492
+ ] }) }),
4493
+ /* @__PURE__ */ jsx(Table.Tbody, { children: tasks.map((task) => /* @__PURE__ */ jsxs(Table.Tr, { children: [
4494
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Badge, { variant: "light", color: taskStatusColors[task.status] || "gray", size: "sm", children: formatStatusLabel(task.status) }) }),
4495
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Badge, { variant: "light", color: taskTypeColors[task.type] || "gray", size: "sm", children: formatStatusLabel(task.type) }) }),
4496
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", children: task.name }) }),
4497
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: projectNameById.get(task.project_id) || "-" }) }),
4498
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "-" }) }),
4499
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: formatTimeAgo(task.created_at) }) })
4500
+ ] }, task.id)) })
4501
+ ] })
4502
+ ] }) })
4503
+ ] }) });
4504
+ }
4505
+ var milestoneStatusOptions = [
4506
+ { value: "upcoming", label: "Upcoming" },
4507
+ { value: "in_progress", label: "In Progress" },
4508
+ { value: "overdue", label: "Overdue" },
4509
+ { value: "blocked", label: "Blocked" }
4510
+ ];
4511
+ function UpcomingMilestonesPage() {
4512
+ const [statusFilter, setStatusFilter] = useState(null);
4513
+ const { data: milestonesRaw, isLoading } = useMilestones({
4514
+ status: statusFilter || void 0
4515
+ });
4516
+ const { data: projects } = useProjects();
4517
+ const projectNameById = useMemo(() => new Map(projects?.map((p) => [p.id, p.name]) ?? []), [projects]);
4518
+ const milestones = useMemo(() => {
4519
+ const list = milestonesRaw ?? [];
4520
+ const filtered = statusFilter ? list : list.filter((m) => m.status !== "completed");
4521
+ return [...filtered].sort((a, b) => {
4522
+ if (!a.due_date && !b.due_date) return 0;
4523
+ if (!a.due_date) return 1;
4524
+ if (!b.due_date) return -1;
4525
+ return a.due_date < b.due_date ? -1 : a.due_date > b.due_date ? 1 : 0;
4526
+ });
4527
+ }, [milestonesRaw, statusFilter]);
4528
+ return /* @__PURE__ */ jsx(SubshellContentContainer, { children: /* @__PURE__ */ jsxs(PageContainer, { children: [
4529
+ /* @__PURE__ */ jsx(Stack, { children: /* @__PURE__ */ jsx(PageTitleCaption, { title: "Upcoming Milestones", caption: "Milestones across all projects sorted by due date" }) }),
4530
+ /* @__PURE__ */ jsx(Paper, { children: /* @__PURE__ */ jsxs(Stack, { children: [
4531
+ /* @__PURE__ */ jsx(
4532
+ Select,
4533
+ {
4534
+ placeholder: "All Statuses",
4535
+ data: milestoneStatusOptions,
4536
+ style: { minWidth: 180, maxWidth: 220 },
4537
+ size: "sm",
4538
+ clearable: true,
4539
+ value: statusFilter,
4540
+ onChange: setStatusFilter,
4541
+ m: "sm"
4542
+ }
4543
+ ),
4544
+ isLoading ? /* @__PURE__ */ jsx(Center, { p: "xl", children: /* @__PURE__ */ jsx(Loader, {}) }) : !milestones.length ? /* @__PURE__ */ jsx(EmptyState, { icon: IconFlag, title: "No upcoming milestones" }) : /* @__PURE__ */ jsxs(Table, { children: [
4545
+ /* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
4546
+ /* @__PURE__ */ jsx(Table.Th, { children: "Status" }),
4547
+ /* @__PURE__ */ jsx(Table.Th, { children: "Name" }),
4548
+ /* @__PURE__ */ jsx(Table.Th, { children: "Project" }),
4549
+ /* @__PURE__ */ jsx(Table.Th, { children: "Due" }),
4550
+ /* @__PURE__ */ jsx(Table.Th, { children: "Progress" })
4551
+ ] }) }),
4552
+ /* @__PURE__ */ jsx(Table.Tbody, { children: milestones.map((milestone) => /* @__PURE__ */ jsxs(Table.Tr, { children: [
4553
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Badge, { variant: "light", color: milestoneStatusColors[milestone.status] || "gray", size: "sm", children: formatStatusLabel(milestone.status) }) }),
4554
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", children: milestone.name }) }),
4555
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: projectNameById.get(milestone.project_id) || "-" }) }),
4556
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: formatDate3(milestone.due_date) }) }),
4557
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "-" }) })
4558
+ ] }, milestone.id)) })
4559
+ ] })
4560
+ ] }) })
4561
+ ] }) });
4562
+ }
3048
4563
  function NotificationPanel({ notifications, isLoading, onClose, onNavigate }) {
3049
4564
  const markAllAsRead = useMarkAllAsRead();
3050
4565
  const hasUnread = notifications.some((n) => !n.read);
@@ -3097,4 +4612,4 @@ function NotificationBell({ unreadCount, onNavigate }) {
3097
4612
  ] });
3098
4613
  }
3099
4614
 
3100
- export { AbsoluteScheduleForm, ApiKeyDisplayModal, ApiKeyList, ApiKeySettings, Breadcrumbs, CrashErrorFallback, CreateApiKeyModal, CreateScheduleModal, DeleteScheduleModal, DeploymentDetailModal, DeploymentList, DeploymentSettings, DeploymentStatusBadge, DocTreeNav, EditApiKeyModal, ErrorReportCard, KnowledgeBasePage, MdxRenderer, NotificationBell, NotificationPanel, RecurringScheduleForm, RelativeScheduleForm, RichTextEditor, ScheduleCard, ScheduleDetailModal, ScheduleTypeSelector, SortableHeader, TableSelectionToolbar, TaskScheduler, buildErrorReport, mdxComponents };
4615
+ export { AbsoluteScheduleForm, ActivityFeedWidget, AllTasksPage, ApiKeyDisplayModal, ApiKeyList, ApiKeySettings, Breadcrumbs, CrashErrorFallback, CreateApiKeyModal, CreateScheduleModal, CrmOverview, DEAL_STAGES, DEFAULT_KANBAN_CONFIG, DealDrawer, DealKanbanCard, DeleteScheduleModal, DeploymentDetailModal, DeploymentList, DeploymentSettings, DeploymentStatusBadge, DocTreeNav, EditApiKeyModal, ErrorReportCard, HealthStatusCard, KanbanBoard, KnowledgeBasePage, MdxRenderer, MetricsStrip, MilestoneTimeline, MyTasksPanel, NotificationBell, NotificationPanel, PIPELINE_FUNNEL_ORDER, PipelineFunnelWidget, ProjectsSidebar, ProjectsSidebarMiddle, ProjectsSidebarTop, QuickCreateActions, RecurringScheduleForm, RelativeScheduleForm, RichTextEditor, SAVED_VIEW_PRESETS, SavedViewsPanel, ScheduleCard, ScheduleDetailModal, ScheduleTypeSelector, SortableHeader, TableSelectionToolbar, TaskCard, TaskScheduler, TasksDueWidget, UpcomingMilestonesPage, buildErrorReport, calculateProgress, formatStatusLabel, mdxComponents, milestoneStatusColors, noteTypeColors, projectStatusColors, taskStatusColors, taskTypeColors, useCrmPipelineSummary, useCrmQuickMetrics, useRecentCrmActivity };