@elevasis/ui 1.24.3 → 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.
- package/dist/charts/index.js +2 -2
- package/dist/{chunk-H3MU3WTM.js → chunk-3EVTCVKR.js} +2 -2
- package/dist/{chunk-ZDKC3V7C.js → chunk-7RS6VTAV.js} +56 -57
- package/dist/{chunk-TQBM3OEW.js → chunk-BS4J2LAW.js} +1 -1
- package/dist/{chunk-JR2C4XAN.js → chunk-CYXZHBP4.js} +202 -295
- package/dist/{chunk-OH74INP2.js → chunk-FEZZ3IDU.js} +434 -314
- package/dist/{chunk-IAZT3VO6.js → chunk-G25YWGUL.js} +4 -1
- package/dist/{chunk-BGTZFEKR.js → chunk-HYYI4ZFT.js} +6 -252
- package/dist/{chunk-CTF6FS2M.js → chunk-L3GVDMCA.js} +211 -1
- package/dist/{chunk-VMMNFRAO.js → chunk-QNABH7YG.js} +3 -3
- package/dist/{chunk-JTUX5FDC.js → chunk-R565P6XC.js} +691 -2
- package/dist/{chunk-WY5IJI37.js → chunk-RIL2CDFE.js} +2 -2
- package/dist/{chunk-TML32XBW.js → chunk-RMPXGBNI.js} +2 -2
- package/dist/{chunk-UG5565XQ.js → chunk-US4JUSI3.js} +3 -3
- package/dist/components/index.d.ts +3005 -192
- package/dist/components/index.js +1539 -24
- package/dist/features/auth/index.d.ts +108 -9
- package/dist/features/dashboard/index.js +7 -7
- package/dist/features/monitoring/index.js +8 -8
- package/dist/features/operations/index.d.ts +8 -2
- package/dist/features/operations/index.js +52 -55
- package/dist/features/settings/index.d.ts +108 -9
- package/dist/features/settings/index.js +27 -10
- package/dist/hooks/index.d.ts +3279 -185
- package/dist/hooks/index.js +4 -4
- package/dist/hooks/published.d.ts +108 -9
- package/dist/hooks/published.js +3 -3
- package/dist/index.d.ts +3285 -187
- package/dist/index.js +5 -5
- package/dist/initialization/index.d.ts +108 -9
- package/dist/layout/index.d.ts +60 -3
- package/dist/layout/index.js +2 -2
- package/dist/profile/index.d.ts +108 -9
- package/dist/provider/index.d.ts +6 -2
- package/dist/provider/index.js +3 -3
- package/dist/provider/published.d.ts +6 -2
- package/dist/supabase/index.d.ts +210 -18
- package/dist/theme/index.d.ts +6 -2
- package/dist/theme/index.js +3 -3
- package/dist/types/index.d.ts +108 -9
- package/package.json +1 -1
package/dist/components/index.js
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
import { useBreadcrumbs } from '../chunk-MG3NF7QL.js';
|
|
2
|
-
import { SubshellContainer, SubshellSidebar, SubshellRightSideContainer, SubshellContentContainer } from '../chunk-
|
|
3
|
-
import { NotificationList } from '../chunk-
|
|
4
|
-
export { ActivityCard, ActivityFilters as ActivityFiltersBar, ActivityTable, BusinessImpactCard, CostBreakdownCard, CostByModelTable, CostMetricsCard, ErrorAnalysisCard, ErrorBreakdownTable, ExecutionBreakdownTable, ExecutionHealthCard, ExecutionLogsFilters as ExecutionLogsFilterBar, ExecutionLogsTable, NotificationItem, NotificationList } from '../chunk-
|
|
5
|
-
export { CreateCredentialModal, CredentialList, CredentialSettings, MembershipFeaturePanel, MembershipStatusBadge, OAuthConnectModal, OrganizationMembershipsList, WebhookUrlDisplayModal } from '../chunk-
|
|
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,
|
|
10
|
-
import { SubshellLoader, PageContainer,
|
|
11
|
-
export { ResourceHealthPanel } from '../chunk-
|
|
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-
|
|
15
|
-
import '../chunk-
|
|
16
|
-
import { ListSkeleton, EmptyState, PageTitleCaption, StatCard,
|
|
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-
|
|
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-
|
|
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
23
|
import { usePaginationState, useDeploymentDocs, useResources, useCreateSchedule, useListSchedules, usePauseSchedule, useResumeSchedule, useCancelSchedule, useDeleteSchedule, useMarkAllAsRead, useNotifications, showErrorNotification, showSuccessNotification, showApiErrorNotification } from '../chunk-QDO6NF2I.js';
|
|
24
24
|
export { showApiErrorNotification, showErrorNotification, showInfoNotification, showSuccessNotification, showWarningNotification } from '../chunk-QDO6NF2I.js';
|
|
25
|
-
import '../chunk-NJJ3NQ7B.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-
|
|
34
|
+
import '../chunk-RMPXGBNI.js';
|
|
35
35
|
export { ElevasisLoader } from '../chunk-SZHARWKU.js';
|
|
36
|
-
import '../chunk-
|
|
37
|
-
import '../chunk-
|
|
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,
|
|
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(
|
|
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(
|
|
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 };
|