@handled-ai/design-system 0.8.0 → 0.9.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/README.md +14 -4
- package/dist/charts/bar-chart-component.d.ts +24 -0
- package/dist/charts/bar-chart-component.js +123 -0
- package/dist/charts/bar-chart-component.js.map +1 -0
- package/dist/charts/chart-tooltip.d.ts +26 -0
- package/dist/charts/chart-tooltip.js +69 -0
- package/dist/charts/chart-tooltip.js.map +1 -0
- package/dist/charts/chart.d.ts +64 -0
- package/dist/charts/chart.js +285 -0
- package/dist/charts/chart.js.map +1 -0
- package/dist/charts/donut-chart.d.ts +21 -0
- package/dist/charts/donut-chart.js +96 -0
- package/dist/charts/donut-chart.js.map +1 -0
- package/dist/charts/index.d.ts +11 -0
- package/dist/charts/index.js +10 -0
- package/dist/charts/index.js.map +1 -0
- package/dist/charts/pipeline-overview.d.ts +76 -0
- package/dist/charts/pipeline-overview.js +372 -0
- package/dist/charts/pipeline-overview.js.map +1 -0
- package/dist/charts/sankey-chart.d.ts +52 -0
- package/dist/charts/sankey-chart.js +219 -0
- package/dist/charts/sankey-chart.js.map +1 -0
- package/dist/charts/top-line-metrics.d.ts +26 -0
- package/dist/charts/top-line-metrics.js +224 -0
- package/dist/charts/top-line-metrics.js.map +1 -0
- package/dist/charts/trend-area-chart.d.ts +21 -0
- package/dist/charts/trend-area-chart.js +150 -0
- package/dist/charts/trend-area-chart.js.map +1 -0
- package/dist/charts/volume-analysis-chart.d.ts +19 -0
- package/dist/charts/volume-analysis-chart.js +121 -0
- package/dist/charts/volume-analysis-chart.js.map +1 -0
- package/dist/components/activity-detail.d.ts +38 -0
- package/dist/components/activity-detail.js +163 -0
- package/dist/components/activity-detail.js.map +1 -0
- package/dist/components/activity-log.d.ts +21 -0
- package/dist/components/activity-log.js +61 -0
- package/dist/components/activity-log.js.map +1 -0
- package/dist/components/agent-popover.d.ts +71 -0
- package/dist/components/agent-popover.js +282 -0
- package/dist/components/agent-popover.js.map +1 -0
- package/dist/components/agent-widget.d.ts +24 -0
- package/dist/components/agent-widget.js +117 -0
- package/dist/components/agent-widget.js.map +1 -0
- package/dist/components/avatar.d.ts +13 -0
- package/dist/components/avatar.js +140 -0
- package/dist/components/avatar.js.map +1 -0
- package/dist/components/badge.d.ts +12 -0
- package/dist/components/badge.js +75 -0
- package/dist/components/badge.js.map +1 -0
- package/dist/components/button.d.ts +13 -0
- package/dist/components/button.js +83 -0
- package/dist/components/button.js.map +1 -0
- package/dist/components/card.d.ts +11 -0
- package/dist/components/card.js +119 -0
- package/dist/components/card.js.map +1 -0
- package/dist/components/contact-list.d.ts +34 -0
- package/dist/components/contact-list.js +84 -0
- package/dist/components/contact-list.js.map +1 -0
- package/dist/components/dashboard-cards.d.ts +10 -0
- package/dist/components/dashboard-cards.js +164 -0
- package/dist/components/dashboard-cards.js.map +1 -0
- package/dist/components/data-table-display.d.ts +19 -0
- package/dist/components/data-table-display.js +109 -0
- package/dist/components/data-table-display.js.map +1 -0
- package/dist/components/data-table-filter.d.ts +18 -0
- package/dist/components/data-table-filter.js +107 -0
- package/dist/components/data-table-filter.js.map +1 -0
- package/dist/components/data-table-quick-views.d.ts +13 -0
- package/dist/components/data-table-quick-views.js +90 -0
- package/dist/components/data-table-quick-views.js.map +1 -0
- package/dist/components/data-table-toolbar.d.ts +18 -0
- package/dist/components/data-table-toolbar.js +45 -0
- package/dist/components/data-table-toolbar.js.map +1 -0
- package/dist/components/data-table.d.ts +39 -0
- package/dist/components/data-table.js +821 -0
- package/dist/components/data-table.js.map +1 -0
- package/dist/components/detail-view.d.ts +44 -0
- package/dist/components/detail-view.js +165 -0
- package/dist/components/detail-view.js.map +1 -0
- package/dist/components/dialog.d.ts +19 -0
- package/dist/components/dialog.js +188 -0
- package/dist/components/dialog.js.map +1 -0
- package/dist/components/dropdown-menu.d.ts +27 -0
- package/dist/components/dropdown-menu.js +279 -0
- package/dist/components/dropdown-menu.js.map +1 -0
- package/dist/components/entity-panel.d.ts +69 -0
- package/dist/components/entity-panel.js +584 -0
- package/dist/components/entity-panel.js.map +1 -0
- package/dist/components/inbox-row.d.ts +27 -0
- package/dist/components/inbox-row.js +139 -0
- package/dist/components/inbox-row.js.map +1 -0
- package/dist/components/inbox-toolbar.d.ts +21 -0
- package/dist/components/inbox-toolbar.js +203 -0
- package/dist/components/inbox-toolbar.js.map +1 -0
- package/dist/components/input.d.ts +5 -0
- package/dist/components/input.js +50 -0
- package/dist/components/input.js.map +1 -0
- package/dist/components/insights-filter-bar.d.ts +21 -0
- package/dist/components/insights-filter-bar.js +99 -0
- package/dist/components/insights-filter-bar.js.map +1 -0
- package/dist/components/item-list-display.d.ts +22 -0
- package/dist/components/item-list-display.js +240 -0
- package/dist/components/item-list-display.js.map +1 -0
- package/dist/components/item-list-filter.d.ts +16 -0
- package/dist/components/item-list-filter.js +87 -0
- package/dist/components/item-list-filter.js.map +1 -0
- package/dist/components/item-list-toolbar.d.ts +25 -0
- package/dist/components/item-list-toolbar.js +79 -0
- package/dist/components/item-list-toolbar.js.map +1 -0
- package/dist/components/item-list.d.ts +20 -0
- package/dist/components/item-list.js +702 -0
- package/dist/components/item-list.js.map +1 -0
- package/dist/components/label.d.ts +6 -0
- package/dist/components/label.js +55 -0
- package/dist/components/label.js.map +1 -0
- package/dist/components/message.d.ts +23 -0
- package/dist/components/message.js +117 -0
- package/dist/components/message.js.map +1 -0
- package/dist/components/metric-card.d.ts +25 -0
- package/dist/components/metric-card.js +107 -0
- package/dist/components/metric-card.js.map +1 -0
- package/dist/components/performance-metrics-table.d.ts +38 -0
- package/dist/components/performance-metrics-table.js +342 -0
- package/dist/components/performance-metrics-table.js.map +1 -0
- package/dist/components/preview-list.d.ts +14 -0
- package/dist/components/preview-list.js +83 -0
- package/dist/components/preview-list.js.map +1 -0
- package/dist/components/progress.d.ts +6 -0
- package/dist/components/progress.js +69 -0
- package/dist/components/progress.js.map +1 -0
- package/dist/components/quick-action-chat-area.d.ts +24 -0
- package/dist/components/quick-action-chat-area.js +178 -0
- package/dist/components/quick-action-chat-area.js.map +1 -0
- package/dist/components/quick-action-modal.d.ts +30 -0
- package/dist/components/quick-action-modal.js +288 -0
- package/dist/components/quick-action-modal.js.map +1 -0
- package/dist/components/quick-action-sidebar-nav.d.ts +51 -0
- package/dist/components/quick-action-sidebar-nav.js +528 -0
- package/dist/components/quick-action-sidebar-nav.js.map +1 -0
- package/dist/components/recommended-actions-section.d.ts +23 -0
- package/dist/components/recommended-actions-section.js +215 -0
- package/dist/components/recommended-actions-section.js.map +1 -0
- package/dist/components/report-card.d.ts +26 -0
- package/dist/components/report-card.js +69 -0
- package/dist/components/report-card.js.map +1 -0
- package/dist/components/score-analysis-modal.d.ts +26 -0
- package/dist/components/score-analysis-modal.js +141 -0
- package/dist/components/score-analysis-modal.js.map +1 -0
- package/dist/components/score-breakdown.d.ts +17 -0
- package/dist/components/score-breakdown.js +162 -0
- package/dist/components/score-breakdown.js.map +1 -0
- package/dist/components/score-feedback.d.ts +40 -0
- package/dist/components/score-feedback.js +209 -0
- package/dist/components/score-feedback.js.map +1 -0
- package/dist/components/score-ring.d.ts +14 -0
- package/dist/components/score-ring.js +79 -0
- package/dist/components/score-ring.js.map +1 -0
- package/dist/components/scroll-area.d.ts +7 -0
- package/dist/components/scroll-area.js +101 -0
- package/dist/components/scroll-area.js.map +1 -0
- package/dist/components/select.d.ts +17 -0
- package/dist/components/select.js +228 -0
- package/dist/components/select.js.map +1 -0
- package/dist/components/separator.d.ts +6 -0
- package/dist/components/separator.js +61 -0
- package/dist/components/separator.js.map +1 -0
- package/dist/components/sheet.d.ts +16 -0
- package/dist/components/sheet.js +168 -0
- package/dist/components/sheet.js.map +1 -0
- package/dist/components/sidebar.d.ts +73 -0
- package/dist/components/sidebar.js +723 -0
- package/dist/components/sidebar.js.map +1 -0
- package/dist/components/signal-feedback-inline.d.ts +51 -0
- package/dist/components/signal-feedback-inline.js +548 -0
- package/dist/components/signal-feedback-inline.js.map +1 -0
- package/dist/components/simple-data-table.d.ts +15 -0
- package/dist/components/simple-data-table.js +91 -0
- package/dist/components/simple-data-table.js.map +1 -0
- package/dist/components/skeleton.d.ts +5 -0
- package/dist/components/skeleton.js +44 -0
- package/dist/components/skeleton.js.map +1 -0
- package/dist/components/status-badge.d.ts +10 -0
- package/dist/components/status-badge.js +82 -0
- package/dist/components/status-badge.js.map +1 -0
- package/dist/components/styled-bar-list.d.ts +20 -0
- package/dist/components/styled-bar-list.js +59 -0
- package/dist/components/styled-bar-list.js.map +1 -0
- package/dist/components/suggested-actions.d.ts +110 -0
- package/dist/components/suggested-actions.js +1538 -0
- package/dist/components/suggested-actions.js.map +1 -0
- package/dist/components/table.d.ts +12 -0
- package/dist/components/table.js +147 -0
- package/dist/components/table.js.map +1 -0
- package/dist/components/tabs.d.ts +14 -0
- package/dist/components/tabs.js +129 -0
- package/dist/components/tabs.js.map +1 -0
- package/dist/components/textarea.d.ts +5 -0
- package/dist/components/textarea.js +47 -0
- package/dist/components/textarea.js.map +1 -0
- package/dist/components/timeline-activity.d.ts +34 -0
- package/dist/components/timeline-activity.js +181 -0
- package/dist/components/timeline-activity.js.map +1 -0
- package/dist/components/tooltip.d.ts +9 -0
- package/dist/components/tooltip.js +93 -0
- package/dist/components/tooltip.js.map +1 -0
- package/dist/components/view-mode-toggle.d.ts +16 -0
- package/dist/components/view-mode-toggle.js +24 -0
- package/dist/components/view-mode-toggle.js.map +1 -0
- package/dist/hooks/use-mobile.d.ts +3 -0
- package/dist/hooks/use-mobile.js +21 -0
- package/dist/hooks/use-mobile.js.map +1 -0
- package/dist/index.d.ts +68 -1878
- package/dist/index.js +69 -10918
- package/dist/index.js.map +1 -1
- package/dist/lib/icons.d.ts +18 -0
- package/dist/lib/icons.js +21 -0
- package/dist/lib/icons.js.map +1 -0
- package/dist/lib/utils.d.ts +5 -0
- package/dist/lib/utils.js +9 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/prototype/index.d.ts +20 -0
- package/dist/prototype/index.js +8 -0
- package/dist/prototype/index.js.map +1 -0
- package/dist/prototype/prototype-accounts-view.d.ts +22 -0
- package/dist/prototype/prototype-accounts-view.js +70 -0
- package/dist/prototype/prototype-accounts-view.js.map +1 -0
- package/dist/prototype/prototype-admin-view.d.ts +21 -0
- package/dist/prototype/prototype-admin-view.js +53 -0
- package/dist/prototype/prototype-admin-view.js.map +1 -0
- package/dist/prototype/prototype-config.d.ts +226 -0
- package/dist/prototype/prototype-config.js +1 -0
- package/dist/prototype/prototype-config.js.map +1 -0
- package/dist/prototype/prototype-inbox-view.d.ts +48 -0
- package/dist/prototype/prototype-inbox-view.js +701 -0
- package/dist/prototype/prototype-inbox-view.js.map +1 -0
- package/dist/prototype/prototype-insights-view.d.ts +23 -0
- package/dist/prototype/prototype-insights-view.js +335 -0
- package/dist/prototype/prototype-insights-view.js.map +1 -0
- package/dist/prototype/prototype-shell.d.ts +40 -0
- package/dist/prototype/prototype-shell.js +190 -0
- package/dist/prototype/prototype-shell.js.map +1 -0
- package/dist/prototype/prototype-work-queue-view.d.ts +8 -0
- package/dist/prototype/prototype-work-queue-view.js +17 -0
- package/dist/prototype/prototype-work-queue-view.js.map +1 -0
- package/dist/three/agent-orb.d.ts +39 -0
- package/dist/three/agent-orb.js +500 -0
- package/dist/three/agent-orb.js.map +1 -0
- package/dist/three/index.d.ts +2 -0
- package/dist/three/index.js +2 -0
- package/dist/three/index.js.map +1 -0
- package/package.json +98 -17
- package/src/charts/bar-chart-component.tsx +150 -0
- package/src/charts/chart-tooltip.tsx +86 -0
- package/src/charts/chart.tsx +371 -0
- package/src/charts/donut-chart.tsx +112 -0
- package/src/charts/index.ts +13 -0
- package/src/charts/pipeline-overview.tsx +476 -0
- package/src/charts/sankey-chart.tsx +290 -0
- package/src/charts/top-line-metrics.tsx +261 -0
- package/src/charts/trend-area-chart.tsx +150 -0
- package/src/charts/volume-analysis-chart.tsx +124 -0
- package/src/components/activity-detail.tsx +233 -0
- package/src/components/activity-log.tsx +89 -0
- package/src/components/agent-popover.tsx +373 -0
- package/src/components/agent-widget.tsx +163 -0
- package/src/components/avatar.tsx +109 -0
- package/src/components/badge.tsx +48 -0
- package/src/components/button.tsx +59 -0
- package/src/components/card.tsx +92 -0
- package/src/components/contact-list.tsx +121 -0
- package/src/components/dashboard-cards.tsx +170 -0
- package/src/components/data-table-display.tsx +139 -0
- package/src/components/data-table-filter.tsx +138 -0
- package/src/components/data-table-quick-views.tsx +103 -0
- package/src/components/data-table-toolbar.tsx +56 -0
- package/src/components/data-table.tsx +915 -0
- package/src/components/detail-view.tsx +237 -0
- package/src/components/dialog.tsx +158 -0
- package/src/components/dropdown-menu.tsx +257 -0
- package/src/components/entity-panel.tsx +767 -0
- package/src/components/inbox-row.tsx +132 -0
- package/src/components/inbox-toolbar.tsx +213 -0
- package/src/components/input.tsx +21 -0
- package/src/components/insights-filter-bar.tsx +132 -0
- package/src/components/item-list-display.tsx +278 -0
- package/src/components/item-list-filter.tsx +118 -0
- package/src/components/item-list-toolbar.tsx +97 -0
- package/src/components/item-list.tsx +843 -0
- package/src/components/label.tsx +24 -0
- package/src/components/message.tsx +83 -0
- package/src/components/metric-card.tsx +178 -0
- package/src/components/performance-metrics-table.tsx +442 -0
- package/src/components/preview-list.tsx +62 -0
- package/src/components/progress.tsx +31 -0
- package/src/components/quick-action-chat-area.tsx +156 -0
- package/src/components/quick-action-modal.tsx +331 -0
- package/src/components/quick-action-sidebar-nav.tsx +592 -0
- package/src/components/recommended-actions-section.tsx +258 -0
- package/src/components/report-card.tsx +106 -0
- package/src/components/score-analysis-modal.tsx +172 -0
- package/src/components/score-breakdown.tsx +179 -0
- package/src/components/score-feedback.tsx +288 -0
- package/src/components/score-ring.tsx +87 -0
- package/src/components/scroll-area.tsx +58 -0
- package/src/components/select.tsx +190 -0
- package/src/components/separator.tsx +28 -0
- package/src/components/sheet.tsx +143 -0
- package/src/components/sidebar.tsx +726 -0
- package/src/components/signal-feedback-inline.tsx +591 -0
- package/src/components/simple-data-table.tsx +124 -0
- package/src/components/skeleton.tsx +15 -0
- package/src/components/status-badge.tsx +63 -0
- package/src/components/styled-bar-list.tsx +70 -0
- package/src/components/suggested-actions.tsx +1985 -0
- package/src/components/table.tsx +116 -0
- package/src/components/tabs.tsx +91 -0
- package/src/components/textarea.tsx +18 -0
- package/src/components/timeline-activity.tsx +234 -0
- package/src/components/tooltip.tsx +57 -0
- package/src/components/view-mode-toggle.tsx +39 -0
- package/src/hooks/use-mobile.ts +21 -0
- package/src/index.ts +77 -0
- package/src/lib/icons.ts +18 -0
- package/src/lib/utils.ts +6 -0
- package/src/prototype/index.ts +11 -0
- package/src/prototype/prototype-accounts-view.tsx +112 -0
- package/src/prototype/prototype-admin-view.tsx +67 -0
- package/src/prototype/prototype-config.ts +243 -0
- package/src/prototype/prototype-inbox-view.tsx +810 -0
- package/src/prototype/prototype-insights-view.tsx +379 -0
- package/src/prototype/prototype-shell.tsx +219 -0
- package/src/prototype/prototype-work-queue-view.tsx +30 -0
- package/src/styles/globals.css +299 -0
- package/src/three/agent-orb.tsx +557 -0
- package/src/three/index.ts +5 -0
- package/src/types/r3f.d.ts +8 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/charts/pipeline-overview.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { TrendingUp, Info, ArrowRight } from \"lucide-react\"\nimport { ResponsiveSankey } from \"@nivo/sankey\"\n\nimport { cn } from \"../lib/utils\"\nimport {\n Tooltip,\n TooltipContent,\n TooltipProvider,\n TooltipTrigger,\n} from \"../components/tooltip\"\n\nexport interface PipelineStage {\n id: string\n label: string\n count: number\n trend: string\n nextConversion: string | null\n}\n\nexport interface PipelineStageMetrics {\n medianTime: string\n avgTime: string\n dropOffs: { reason: string; count: number; pct: string }[]\n}\n\nexport interface PipelineStageTiming {\n median: string\n avg: string\n}\n\nexport interface PipelineFilterBreakdown {\n [stageId: string]: Record<string, number>\n}\n\nconst SEGMENT_PALETTE = [\n \"#0F4C3A\",\n \"#15803d\",\n \"#0ea5e9\",\n \"#8b5cf6\",\n \"#f59e0b\",\n \"#ef4444\",\n]\n\nconst DROP_OFF_NODES = [\n { id: \"Lost/Other\", nodeColor: \"#CBD5E1\" },\n { id: \"Coverage\", nodeColor: \"#F59E0B\" },\n { id: \"Unqualified\", nodeColor: \"#F59E0B\" },\n { id: \"No Contact\", nodeColor: \"#F59E0B\" },\n { id: \"Intake Drop\", nodeColor: \"#F59E0B\" },\n { id: \"No Show/Cancel\", nodeColor: \"#F59E0B\" },\n]\n\nfunction StageHoverCard({\n title,\n count,\n metrics,\n}: {\n title: string\n count: number | string\n metrics?: PipelineStageMetrics\n}) {\n return (\n <div className=\"w-[260px] overflow-hidden rounded-lg border border-border bg-card font-sans text-left shadow-xl\">\n <div className=\"border-b border-border p-3\">\n <div className=\"mb-0.5 text-xs font-medium text-muted-foreground\">{title}</div>\n <div className=\"text-2xl font-bold text-foreground\">{count}</div>\n </div>\n\n {metrics && (\n <div className=\"grid grid-cols-2 gap-2 border-b border-border bg-muted/30 px-3 py-2\">\n <div>\n <div className=\"text-[9px] font-semibold uppercase tracking-wider text-muted-foreground/70\">\n Median\n </div>\n <div className=\"text-xs font-bold text-foreground\">\n {metrics.medianTime}\n </div>\n </div>\n <div className=\"border-l border-border pl-2\">\n <div className=\"text-[9px] font-semibold uppercase tracking-wider text-muted-foreground/70\">\n Average\n </div>\n <div className=\"text-xs font-bold text-foreground\">\n {metrics.avgTime}\n </div>\n </div>\n </div>\n )}\n\n {metrics?.dropOffs && metrics.dropOffs.length > 0 && (\n <div className=\"p-2\">\n <div className=\"mb-1.5 px-1 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground\">\n Drop-off Reasons\n </div>\n <div className=\"space-y-0.5\">\n {metrics.dropOffs.map((drop, i) => (\n <div\n key={i}\n className=\"group flex cursor-pointer items-center justify-between rounded p-1.5 text-xs transition-colors hover:bg-muted\"\n >\n <span className=\"truncate pr-2 text-muted-foreground group-hover:text-foreground\">\n {drop.reason}\n </span>\n <div className=\"flex items-center gap-1.5\">\n <span className=\"text-[10px] text-muted-foreground/70\">{drop.pct}</span>\n <span className=\"inline-flex h-4 min-w-[20px] items-center justify-center rounded border border-border bg-muted px-1 text-[9px] font-semibold text-muted-foreground group-hover:bg-muted group-hover:text-foreground\">\n {drop.count}\n </span>\n </div>\n </div>\n ))}\n </div>\n </div>\n )}\n\n <div className=\"flex cursor-pointer items-center justify-between border-t border-border bg-muted px-3 py-2 text-xs font-medium text-blue-600 transition-colors hover:bg-muted hover:text-blue-700\">\n View in Work Queue <ArrowRight className=\"h-3 w-3\" />\n </div>\n </div>\n )\n}\n\nexport interface PipelineOverviewProps {\n title?: string\n stages: PipelineStage[]\n stageMetrics: Record<string, PipelineStageMetrics>\n stageTimings: (PipelineStageTiming | null)[]\n filterOptions?: string[]\n onFilterChange?: (filterOption: string) => void\n filterBreakdowns?: Record<string, PipelineFilterBreakdown>\n countingModes?: string[]\n countingModeTooltip?: string\n /** Main pipeline flow nodes (after the first stage) */\n flowNodes?: { id: string; nodeColor: string }[]\n /** Drop-off distribution from initial stage: { \"Lost/Other\": 56, \"Coverage\": 40, ... } */\n dropOffDistribution?: Record<string, number>\n /** Flow links after the first stage (middle of pipeline onward) */\n flowLinks?: { source: string; target: string; value: number }[]\n totalReceived?: number\n /** Dollar amounts to display on terminal node labels: { \"Retained\": \"$18.2M\" } */\n nodeAmounts?: Record<string, string>\n /** Unit noun for standard node/link tooltips (e.g. \"signals\", \"accounts\"). */\n unitLabel?: string\n /** Unit noun for terminal/drop-off node tooltips (e.g. \"opportunities\"). Defaults to unitLabel. */\n terminalUnitLabel?: string\n /** Node IDs that should use terminalUnitLabel. Defaults to keys of dropOffDistribution. */\n terminalNodeIds?: string[]\n /** Sankey chart margins, merged with defaults { top: 20, right: 120, bottom: 20, left: 140 }. */\n sankeyMargin?: { top?: number; right?: number; bottom?: number; left?: number }\n /** Gap between Sankey node bar and its outside label. */\n sankeyLabelPadding?: number\n /** When true, conversion badges use `flex`; when false, `hidden xl:flex`. */\n alwaysShowConversionBadges?: boolean\n /** Color for dynamically generated drop-off nodes. */\n dropOffNodeColor?: string\n onViewInWorkQueue?: (stageId: string) => void\n className?: string\n}\n\nexport function PipelineOverview({\n title = \"Pipeline Overview\",\n stages,\n stageMetrics,\n stageTimings,\n filterOptions = [\"Facility\", \"Source\", \"Lead Source\", \"Payer\", \"Channel\"],\n onFilterChange,\n filterBreakdowns,\n countingModes = [\"Unique Patients\", \"All Referrals\"],\n countingModeTooltip = \"Patients may be referred through multiple channels. 'All Referrals' shows total volume; 'Unique Patients' deduplicates to show distinct patient counts.\",\n flowNodes = [\n { id: \"Contacted\", nodeColor: \"#2A8F7A\" },\n { id: \"Intake Sent\", nodeColor: \"#3DB4A0\" },\n { id: \"Intake Done\", nodeColor: \"#4CC9B0\" },\n { id: \"Scheduled\", nodeColor: \"#5FCFBC\" },\n { id: \"Completed\", nodeColor: \"#79E2C9\" },\n ],\n dropOffDistribution = {\n \"Lost/Other\": 56,\n Coverage: 40,\n Unqualified: 30,\n },\n flowLinks = [\n { source: \"Contacted\", target: \"Intake Sent\", value: 660 },\n { source: \"Contacted\", target: \"No Contact\", value: 60 },\n { source: \"Intake Sent\", target: \"Intake Done\", value: 612 },\n { source: \"Intake Sent\", target: \"Intake Drop\", value: 48 },\n { source: \"Intake Done\", target: \"Scheduled\", value: 612 },\n { source: \"Scheduled\", target: \"Completed\", value: 520 },\n { source: \"Scheduled\", target: \"No Show/Cancel\", value: 92 },\n ],\n totalReceived = 847,\n nodeAmounts,\n unitLabel,\n terminalUnitLabel,\n terminalNodeIds,\n sankeyMargin,\n sankeyLabelPadding,\n alwaysShowConversionBadges = false,\n dropOffNodeColor,\n onViewInWorkQueue: _onViewInWorkQueue,\n className,\n}: PipelineOverviewProps) {\n const [selectedFilter, setSelectedFilter] = React.useState(filterOptions[0])\n const [countingMode, setCountingMode] = React.useState(countingModes[0])\n\n const effectiveUnitLabel = unitLabel ?? \"items\"\n const effectiveTerminalUnitLabel = terminalUnitLabel ?? effectiveUnitLabel\n const effectiveTerminalNodeIds = React.useMemo(\n () => new Set(terminalNodeIds ?? Object.keys(dropOffDistribution)),\n [terminalNodeIds, dropOffDistribution],\n )\n const effectiveMargin = React.useMemo(\n () => ({ top: 20, right: 120, bottom: 20, left: 140, ...sankeyMargin }),\n [sankeyMargin],\n )\n const effectiveLabelPadding = sankeyLabelPadding ?? 16\n\n const sankeyData = React.useMemo(() => {\n const breakdown =\n filterBreakdowns?.[selectedFilter]?.received ??\n ({ [stages[0]?.label ?? \"Received\"]: totalReceived } as Record<\n string,\n number\n >)\n\n const segments = Object.entries(breakdown).map(([name, value], index) => ({\n id: name,\n nodeColor: SEGMENT_PALETTE[index % SEGMENT_PALETTE.length],\n value,\n }))\n\n const dropOffKeys = Object.keys(dropOffDistribution)\n const dropOffNodes = dropOffKeys.length > 0\n ? dropOffKeys.map((reason) => ({\n id: reason,\n nodeColor: dropOffNodeColor ?? \"#F59E0B\",\n }))\n : DROP_OFF_NODES\n\n const nodes = [\n ...segments,\n ...flowNodes,\n ...dropOffNodes,\n ]\n\n const nodeIds = new Set(nodes.map((n) => n.id))\n for (const link of flowLinks) {\n for (const endpoint of [link.source, link.target]) {\n if (!nodeIds.has(endpoint)) {\n nodes.push({ id: endpoint, nodeColor: dropOffNodeColor ?? \"#F59E0B\" })\n nodeIds.add(endpoint)\n }\n }\n }\n\n const links: { source: string; target: string; value: number }[] = []\n\n const firstFlowNode = flowNodes[0]?.id ?? \"Contacted\"\n\n segments.forEach((segment) => {\n if (segment.value > 0) {\n links.push({\n source: segment.id,\n target: firstFlowNode,\n value: segment.value,\n })\n }\n })\n\n for (const [reason, count] of Object.entries(dropOffDistribution)) {\n if (count > 0) {\n links.push({ source: firstFlowNode, target: reason, value: count })\n }\n }\n\n flowLinks.forEach((link) => links.push({ ...link }))\n\n return { nodes, links }\n }, [\n selectedFilter,\n filterBreakdowns,\n stages,\n totalReceived,\n flowNodes,\n dropOffDistribution,\n dropOffNodeColor,\n flowLinks,\n ])\n\n return (\n <div\n className={cn(\n \"rounded-xl border border-border bg-card p-5 shadow-sm\",\n className,\n )}\n >\n {/* Header */}\n <div className=\"mb-6 flex flex-col gap-4\">\n <div className=\"flex flex-col gap-4 md:flex-row md:items-center md:justify-between\">\n <div className=\"flex items-center gap-4\">\n <h3 className=\"text-lg font-semibold text-foreground\">{title}</h3>\n\n {/* Counting Mode Toggle */}\n {countingModes.length > 1 && (\n <div className=\"flex items-center gap-2 rounded-lg border border-border bg-muted p-1\">\n {countingModes.map((mode) => (\n <button\n key={mode}\n onClick={() => setCountingMode(mode)}\n className={cn(\n \"rounded-md px-3 py-1 text-xs font-medium transition-all\",\n countingMode === mode\n ? \"border border-border bg-background text-foreground shadow-sm\"\n : \"text-muted-foreground hover:text-foreground\",\n )}\n >\n {mode}\n </button>\n ))}\n <TooltipProvider>\n <Tooltip>\n <TooltipTrigger asChild>\n <Info className=\"ml-1 h-3.5 w-3.5 cursor-help text-muted-foreground/70\" />\n </TooltipTrigger>\n <TooltipContent className=\"max-w-[250px] text-xs\">\n {countingModeTooltip}\n </TooltipContent>\n </Tooltip>\n </TooltipProvider>\n </div>\n )}\n </div>\n\n {/* Filter Tabs */}\n {filterOptions.length > 1 && (\n <div className=\"flex items-center gap-1 self-start rounded-lg bg-muted p-1 md:self-auto\">\n {filterOptions.map((option) => (\n <button\n key={option}\n onClick={() => { setSelectedFilter(option); onFilterChange?.(option); }}\n className={cn(\n \"h-7 rounded-md border-none bg-transparent px-3 text-xs font-medium shadow-none transition-all hover:bg-background\",\n selectedFilter === option &&\n \"bg-background text-foreground shadow-sm\",\n )}\n >\n {option}\n </button>\n ))}\n </div>\n )}\n </div>\n </div>\n\n {/* Stage Metrics Row */}\n <div className=\"mb-2 grid gap-4\" style={{ gridTemplateColumns: `repeat(${stages.length}, minmax(0, 1fr))` }}>\n {stages.map((stage, index) => {\n const details = stageMetrics[stage.id]\n const timing = stageTimings[index] ?? null\n\n return (\n <TooltipProvider key={stage.id} delayDuration={100}>\n <Tooltip>\n <TooltipTrigger asChild>\n <div className=\"group relative flex cursor-pointer flex-col items-center rounded-lg p-2 text-center transition-colors hover:bg-muted\">\n <div\n className=\"mb-1 w-full truncate text-xs font-medium text-muted-foreground\"\n title={stage.label}\n >\n {stage.label}\n </div>\n <div className=\"mb-1 text-2xl font-bold text-foreground\">\n {stage.count.toLocaleString()}\n </div>\n <div className=\"mb-1 flex items-center justify-center gap-1 text-xs font-medium text-emerald-600\">\n {stage.trend} <TrendingUp className=\"h-3 w-3\" />\n </div>\n\n {/* Conversion badge + timing between stages */}\n {index < stages.length - 1 && stage.nextConversion && (\n <div className={cn(\n alwaysShowConversionBadges ? \"flex\" : \"hidden xl:flex\",\n \"absolute -right-2 top-1/2 z-10 -translate-y-1/2 translate-x-1/2 flex-col items-center\",\n )}>\n <span className=\"z-10 whitespace-nowrap rounded-full border border-border bg-muted px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground shadow-sm\">\n {stage.nextConversion}\n </span>\n {timing && (\n <div className=\"mt-1 flex flex-col items-center\">\n <div className=\"h-2 w-px bg-border\" />\n <div className=\"whitespace-nowrap rounded bg-background/80 px-1 py-0.5 text-[9px] font-medium leading-3 text-muted-foreground/70 backdrop-blur-[2px]\">\n <span className=\"mr-1 font-semibold text-muted-foreground\">\n Med: {timing.median}\n </span>\n <span className=\"text-muted-foreground/70\">\n Avg: {timing.avg}\n </span>\n </div>\n </div>\n )}\n </div>\n )}\n\n {/* Connector line down to Sankey */}\n <div className=\"mx-auto mb-1 mt-2 h-4 w-px bg-border\" />\n </div>\n </TooltipTrigger>\n <TooltipContent\n className=\"border-none bg-transparent p-0 shadow-none\"\n sideOffset={8}\n >\n <StageHoverCard\n title={stage.label}\n count={stage.count}\n metrics={details}\n />\n </TooltipContent>\n </Tooltip>\n </TooltipProvider>\n )\n })}\n </div>\n\n {/* Sankey Chart */}\n <div\n className=\"relative mt-4 w-full\"\n style={{ height: 400, minWidth: 0 }}\n >\n <ResponsiveSankey\n data={sankeyData}\n margin={effectiveMargin}\n align=\"justify\"\n colors={(node: { nodeColor?: string }) => node.nodeColor || \"#94a3b8\"}\n nodeOpacity={1}\n nodeHoverOthersOpacity={0.35}\n nodeThickness={18}\n nodeSpacing={16}\n nodeBorderWidth={0}\n nodeBorderRadius={3}\n linkOpacity={0.5}\n linkHoverOthersOpacity={0.1}\n linkContract={3}\n enableLinkGradient\n labelPosition=\"outside\"\n labelOrientation=\"horizontal\"\n labelPadding={effectiveLabelPadding}\n label={(node: { id: string }) => nodeAmounts?.[node.id] ? `${node.id} (${nodeAmounts[node.id]})` : node.id}\n labelTextColor={{ from: \"color\", modifiers: [[\"darker\", 1]] }}\n nodeTooltip={({ node }: { node: { id: string; value: number } }) => {\n const unit = effectiveTerminalNodeIds.has(node.id) ? effectiveTerminalUnitLabel : effectiveUnitLabel\n return (\n <div className=\"rounded-md border border-border bg-card p-2 text-xs shadow-lg\">\n <span className=\"mb-1 block font-bold\">{node.id}{nodeAmounts?.[node.id] ? ` — ${nodeAmounts[node.id]}` : \"\"}</span>\n <span>{node.value} {unit}</span>\n </div>\n )\n }}\n linkTooltip={({ link }: { link: { source: { id: string }; target: { id: string }; value: number } }) => {\n const unit = effectiveTerminalNodeIds.has(link.target.id) ? effectiveTerminalUnitLabel : effectiveUnitLabel\n return (\n <div className=\"rounded-md border border-border bg-card p-2 text-xs shadow-lg\">\n <span className=\"mb-1 block font-bold\">\n {link.source.id} → {link.target.id}\n </span>\n <span>{link.value} {unit}</span>\n </div>\n )\n }}\n />\n </div>\n </div>\n )\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
interface SankeyNode {
|
|
4
|
+
id: string;
|
|
5
|
+
nodeColor?: string;
|
|
6
|
+
}
|
|
7
|
+
interface SankeyLink {
|
|
8
|
+
source: string;
|
|
9
|
+
target: string;
|
|
10
|
+
value: number;
|
|
11
|
+
}
|
|
12
|
+
interface SankeyData {
|
|
13
|
+
nodes: SankeyNode[];
|
|
14
|
+
links: SankeyLink[];
|
|
15
|
+
}
|
|
16
|
+
interface SankeyDropOff {
|
|
17
|
+
reason: string;
|
|
18
|
+
count: number;
|
|
19
|
+
pct?: string;
|
|
20
|
+
}
|
|
21
|
+
interface SankeyStageMetrics {
|
|
22
|
+
conversion: string;
|
|
23
|
+
medianTime: string;
|
|
24
|
+
avgTime: string;
|
|
25
|
+
}
|
|
26
|
+
interface SankeyHoverCardData {
|
|
27
|
+
title: string;
|
|
28
|
+
count: number | string;
|
|
29
|
+
metrics?: SankeyStageMetrics;
|
|
30
|
+
dropOffs?: SankeyDropOff[];
|
|
31
|
+
}
|
|
32
|
+
interface SankeyChartProps {
|
|
33
|
+
data: SankeyData;
|
|
34
|
+
height?: number;
|
|
35
|
+
nodeOpacity?: number;
|
|
36
|
+
nodeThickness?: number;
|
|
37
|
+
nodeBorderWidth?: number;
|
|
38
|
+
linkOpacity?: number;
|
|
39
|
+
linkHoverOpacity?: number;
|
|
40
|
+
enableLabels?: boolean;
|
|
41
|
+
labelTextColor?: string;
|
|
42
|
+
stageMetrics?: Record<string, {
|
|
43
|
+
metrics: SankeyStageMetrics;
|
|
44
|
+
dropOffs: SankeyDropOff[];
|
|
45
|
+
}>;
|
|
46
|
+
onDropOffClick?: (reason: string) => void;
|
|
47
|
+
onViewDetails?: (nodeId: string) => void;
|
|
48
|
+
className?: string;
|
|
49
|
+
}
|
|
50
|
+
declare function SankeyChart({ data, height, nodeOpacity, nodeThickness, nodeBorderWidth, linkOpacity, linkHoverOpacity, enableLabels, labelTextColor, stageMetrics, onDropOffClick, onViewDetails, className, }: SankeyChartProps): React.JSX.Element;
|
|
51
|
+
|
|
52
|
+
export { SankeyChart, type SankeyData, type SankeyDropOff, type SankeyHoverCardData, type SankeyLink, type SankeyNode, type SankeyStageMetrics };
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
"use client";
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __defProps = Object.defineProperties;
|
|
6
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
7
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
10
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
11
|
+
var __spreadValues = (a, b) => {
|
|
12
|
+
for (var prop in b || (b = {}))
|
|
13
|
+
if (__hasOwnProp.call(b, prop))
|
|
14
|
+
__defNormalProp(a, prop, b[prop]);
|
|
15
|
+
if (__getOwnPropSymbols)
|
|
16
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
17
|
+
if (__propIsEnum.call(b, prop))
|
|
18
|
+
__defNormalProp(a, prop, b[prop]);
|
|
19
|
+
}
|
|
20
|
+
return a;
|
|
21
|
+
};
|
|
22
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
23
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
24
|
+
import * as React from "react";
|
|
25
|
+
import { ResponsiveSankey } from "@nivo/sankey";
|
|
26
|
+
import { cn } from "../lib/utils.js";
|
|
27
|
+
function HoverCard({
|
|
28
|
+
data,
|
|
29
|
+
position,
|
|
30
|
+
onDropOffClick,
|
|
31
|
+
onViewDetails
|
|
32
|
+
}) {
|
|
33
|
+
return /* @__PURE__ */ jsxs(
|
|
34
|
+
"div",
|
|
35
|
+
{
|
|
36
|
+
className: "pointer-events-auto absolute z-50 w-[260px] overflow-hidden rounded-lg border border-border bg-card font-sans text-left shadow-xl",
|
|
37
|
+
style: { left: position.x + 10, top: position.y - 40 },
|
|
38
|
+
children: [
|
|
39
|
+
/* @__PURE__ */ jsxs("div", { className: "border-b border-border p-3", children: [
|
|
40
|
+
/* @__PURE__ */ jsx("div", { className: "mb-0.5 text-xs font-medium text-muted-foreground", children: data.title }),
|
|
41
|
+
/* @__PURE__ */ jsx("div", { className: "text-2xl font-bold text-foreground", children: data.count })
|
|
42
|
+
] }),
|
|
43
|
+
data.metrics ? /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-3 gap-2 border-b border-border bg-muted/30 px-3 py-2", children: [
|
|
44
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
45
|
+
/* @__PURE__ */ jsx("div", { className: "text-[9px] font-semibold uppercase tracking-wider text-muted-foreground/70", children: "Conversion" }),
|
|
46
|
+
/* @__PURE__ */ jsx("div", { className: "text-xs font-bold text-emerald-600", children: data.metrics.conversion })
|
|
47
|
+
] }),
|
|
48
|
+
/* @__PURE__ */ jsxs("div", { className: "border-l border-border pl-2", children: [
|
|
49
|
+
/* @__PURE__ */ jsx("div", { className: "text-[9px] font-semibold uppercase tracking-wider text-muted-foreground/70", children: "Median" }),
|
|
50
|
+
/* @__PURE__ */ jsx("div", { className: "text-xs font-bold text-foreground", children: data.metrics.medianTime })
|
|
51
|
+
] }),
|
|
52
|
+
/* @__PURE__ */ jsxs("div", { className: "border-l border-border pl-2", children: [
|
|
53
|
+
/* @__PURE__ */ jsx("div", { className: "text-[9px] font-semibold uppercase tracking-wider text-muted-foreground/70", children: "Average" }),
|
|
54
|
+
/* @__PURE__ */ jsx("div", { className: "text-xs font-bold text-foreground", children: data.metrics.avgTime })
|
|
55
|
+
] })
|
|
56
|
+
] }) : null,
|
|
57
|
+
data.dropOffs && data.dropOffs.length > 0 ? /* @__PURE__ */ jsxs("div", { className: "p-2", children: [
|
|
58
|
+
/* @__PURE__ */ jsx("div", { className: "mb-1.5 px-1 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground", children: "Drop-off Reasons" }),
|
|
59
|
+
/* @__PURE__ */ jsx("div", { className: "space-y-0.5", children: data.dropOffs.map((drop, i) => /* @__PURE__ */ jsxs(
|
|
60
|
+
"div",
|
|
61
|
+
{
|
|
62
|
+
onClick: (e) => {
|
|
63
|
+
e.stopPropagation();
|
|
64
|
+
onDropOffClick == null ? void 0 : onDropOffClick(drop.reason);
|
|
65
|
+
},
|
|
66
|
+
className: "group flex cursor-pointer items-center justify-between rounded p-1.5 text-xs transition-colors hover:bg-muted",
|
|
67
|
+
children: [
|
|
68
|
+
/* @__PURE__ */ jsx("span", { className: "truncate pr-2 text-muted-foreground group-hover:text-foreground", children: drop.reason }),
|
|
69
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
70
|
+
drop.pct ? /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground/70", children: drop.pct }) : null,
|
|
71
|
+
/* @__PURE__ */ jsx("span", { className: "inline-flex h-4 min-w-[20px] items-center justify-center rounded border border-border bg-muted px-1 text-[9px] font-semibold text-muted-foreground group-hover:bg-muted group-hover:text-foreground", children: drop.count })
|
|
72
|
+
] })
|
|
73
|
+
]
|
|
74
|
+
},
|
|
75
|
+
i
|
|
76
|
+
)) })
|
|
77
|
+
] }) : null,
|
|
78
|
+
onViewDetails ? /* @__PURE__ */ jsxs(
|
|
79
|
+
"div",
|
|
80
|
+
{
|
|
81
|
+
onClick: (e) => {
|
|
82
|
+
e.stopPropagation();
|
|
83
|
+
onViewDetails();
|
|
84
|
+
},
|
|
85
|
+
className: "flex cursor-pointer items-center justify-between border-t border-border bg-muted px-3 py-2 text-xs font-medium text-blue-600 transition-colors hover:bg-muted hover:text-blue-700",
|
|
86
|
+
children: [
|
|
87
|
+
"View Details",
|
|
88
|
+
/* @__PURE__ */ jsx(
|
|
89
|
+
"svg",
|
|
90
|
+
{
|
|
91
|
+
className: "h-3 w-3",
|
|
92
|
+
fill: "none",
|
|
93
|
+
viewBox: "0 0 24 24",
|
|
94
|
+
stroke: "currentColor",
|
|
95
|
+
strokeWidth: 2,
|
|
96
|
+
children: /* @__PURE__ */ jsx(
|
|
97
|
+
"path",
|
|
98
|
+
{
|
|
99
|
+
strokeLinecap: "round",
|
|
100
|
+
strokeLinejoin: "round",
|
|
101
|
+
d: "M9 5l7 7-7 7"
|
|
102
|
+
}
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
)
|
|
106
|
+
]
|
|
107
|
+
}
|
|
108
|
+
) : null
|
|
109
|
+
]
|
|
110
|
+
}
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
function SankeyChart({
|
|
114
|
+
data,
|
|
115
|
+
height = 400,
|
|
116
|
+
nodeOpacity = 1,
|
|
117
|
+
nodeThickness = 18,
|
|
118
|
+
nodeBorderWidth = 0,
|
|
119
|
+
linkOpacity = 0.25,
|
|
120
|
+
linkHoverOpacity = 0.5,
|
|
121
|
+
enableLabels = true,
|
|
122
|
+
labelTextColor = "#334155",
|
|
123
|
+
stageMetrics,
|
|
124
|
+
onDropOffClick,
|
|
125
|
+
onViewDetails,
|
|
126
|
+
className
|
|
127
|
+
}) {
|
|
128
|
+
const [hoveredNode, setHoveredNode] = React.useState(
|
|
129
|
+
null
|
|
130
|
+
);
|
|
131
|
+
const containerRef = React.useRef(null);
|
|
132
|
+
const handleNodeHover = React.useCallback(
|
|
133
|
+
(node, event) => {
|
|
134
|
+
var _a, _b;
|
|
135
|
+
if (hoveredNode == null ? void 0 : hoveredNode.pinned) return;
|
|
136
|
+
const containerRect = (_a = containerRef.current) == null ? void 0 : _a.getBoundingClientRect();
|
|
137
|
+
if (!containerRect) return;
|
|
138
|
+
const nodeId = node.id;
|
|
139
|
+
const stageKey = nodeId.toLowerCase().replace(/[^a-z]/g, "");
|
|
140
|
+
const meta = (stageMetrics == null ? void 0 : stageMetrics[stageKey]) || (stageMetrics == null ? void 0 : stageMetrics[nodeId]);
|
|
141
|
+
const hoverData = {
|
|
142
|
+
title: nodeId,
|
|
143
|
+
count: (_b = node.value) != null ? _b : 0,
|
|
144
|
+
metrics: meta == null ? void 0 : meta.metrics,
|
|
145
|
+
dropOffs: meta == null ? void 0 : meta.dropOffs
|
|
146
|
+
};
|
|
147
|
+
setHoveredNode({
|
|
148
|
+
id: nodeId,
|
|
149
|
+
x: event.clientX - containerRect.left,
|
|
150
|
+
y: event.clientY - containerRect.top,
|
|
151
|
+
data: hoverData
|
|
152
|
+
});
|
|
153
|
+
},
|
|
154
|
+
[hoveredNode == null ? void 0 : hoveredNode.pinned, stageMetrics]
|
|
155
|
+
);
|
|
156
|
+
const handleNodeLeave = React.useCallback(() => {
|
|
157
|
+
if (!(hoveredNode == null ? void 0 : hoveredNode.pinned)) {
|
|
158
|
+
setHoveredNode(null);
|
|
159
|
+
}
|
|
160
|
+
}, [hoveredNode == null ? void 0 : hoveredNode.pinned]);
|
|
161
|
+
const handleClick = React.useCallback(() => {
|
|
162
|
+
if (hoveredNode == null ? void 0 : hoveredNode.pinned) {
|
|
163
|
+
setHoveredNode(null);
|
|
164
|
+
} else if (hoveredNode) {
|
|
165
|
+
setHoveredNode((prev) => prev ? __spreadProps(__spreadValues({}, prev), { pinned: true }) : null);
|
|
166
|
+
}
|
|
167
|
+
}, [hoveredNode]);
|
|
168
|
+
return /* @__PURE__ */ jsxs(
|
|
169
|
+
"div",
|
|
170
|
+
{
|
|
171
|
+
ref: containerRef,
|
|
172
|
+
className: cn("relative", className),
|
|
173
|
+
style: { height },
|
|
174
|
+
onClick: handleClick,
|
|
175
|
+
children: [
|
|
176
|
+
/* @__PURE__ */ jsx(
|
|
177
|
+
ResponsiveSankey,
|
|
178
|
+
__spreadValues({
|
|
179
|
+
data,
|
|
180
|
+
margin: { top: 20, right: 160, bottom: 20, left: 20 },
|
|
181
|
+
align: "justify",
|
|
182
|
+
colors: (node) => node.nodeColor || "#94a3b8",
|
|
183
|
+
nodeOpacity,
|
|
184
|
+
nodeHoverOpacity: 1,
|
|
185
|
+
nodeThickness,
|
|
186
|
+
nodeSpacing: 24,
|
|
187
|
+
nodeBorderWidth,
|
|
188
|
+
nodeBorderColor: { from: "color", modifiers: [["darker", 0.8]] },
|
|
189
|
+
nodeBorderRadius: 3,
|
|
190
|
+
linkOpacity,
|
|
191
|
+
linkHoverOpacity,
|
|
192
|
+
linkHoverOthersOpacity: 0.1,
|
|
193
|
+
linkContract: 3,
|
|
194
|
+
enableLinkGradient: true,
|
|
195
|
+
labelPosition: "outside",
|
|
196
|
+
labelOrientation: "horizontal",
|
|
197
|
+
labelPadding: 16,
|
|
198
|
+
labelTextColor,
|
|
199
|
+
enableLabels,
|
|
200
|
+
animate: true
|
|
201
|
+
}, { onNodeMouseEnter: handleNodeHover, onNodeMouseLeave: handleNodeLeave })
|
|
202
|
+
),
|
|
203
|
+
hoveredNode ? /* @__PURE__ */ jsx(
|
|
204
|
+
HoverCard,
|
|
205
|
+
{
|
|
206
|
+
data: hoveredNode.data,
|
|
207
|
+
position: { x: hoveredNode.x, y: hoveredNode.y },
|
|
208
|
+
onDropOffClick,
|
|
209
|
+
onViewDetails: onViewDetails ? () => onViewDetails(hoveredNode.id) : void 0
|
|
210
|
+
}
|
|
211
|
+
) : null
|
|
212
|
+
]
|
|
213
|
+
}
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
export {
|
|
217
|
+
SankeyChart
|
|
218
|
+
};
|
|
219
|
+
//# sourceMappingURL=sankey-chart.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/charts/sankey-chart.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { ResponsiveSankey } from \"@nivo/sankey\"\n\nimport { cn } from \"../lib/utils\"\n\nexport interface SankeyNode {\n id: string\n nodeColor?: string\n}\n\nexport interface SankeyLink {\n source: string\n target: string\n value: number\n}\n\nexport interface SankeyData {\n nodes: SankeyNode[]\n links: SankeyLink[]\n}\n\nexport interface SankeyDropOff {\n reason: string\n count: number\n pct?: string\n}\n\nexport interface SankeyStageMetrics {\n conversion: string\n medianTime: string\n avgTime: string\n}\n\nexport interface SankeyHoverCardData {\n title: string\n count: number | string\n metrics?: SankeyStageMetrics\n dropOffs?: SankeyDropOff[]\n}\n\ninterface HoveredNodeState {\n id: string\n x: number\n y: number\n data: SankeyHoverCardData\n pinned?: boolean\n}\n\ninterface SankeyChartProps {\n data: SankeyData\n height?: number\n nodeOpacity?: number\n nodeThickness?: number\n nodeBorderWidth?: number\n linkOpacity?: number\n linkHoverOpacity?: number\n enableLabels?: boolean\n labelTextColor?: string\n stageMetrics?: Record<string, { metrics: SankeyStageMetrics; dropOffs: SankeyDropOff[] }>\n onDropOffClick?: (reason: string) => void\n onViewDetails?: (nodeId: string) => void\n className?: string\n}\n\nfunction HoverCard({\n data,\n position,\n onDropOffClick,\n onViewDetails,\n}: {\n data: SankeyHoverCardData\n position: { x: number; y: number }\n onDropOffClick?: (reason: string) => void\n onViewDetails?: () => void\n}) {\n return (\n <div\n className=\"pointer-events-auto absolute z-50 w-[260px] overflow-hidden rounded-lg border border-border bg-card font-sans text-left shadow-xl\"\n style={{ left: position.x + 10, top: position.y - 40 }}\n >\n <div className=\"border-b border-border p-3\">\n <div className=\"mb-0.5 text-xs font-medium text-muted-foreground\">\n {data.title}\n </div>\n <div className=\"text-2xl font-bold text-foreground\">{data.count}</div>\n </div>\n\n {data.metrics ? (\n <div className=\"grid grid-cols-3 gap-2 border-b border-border bg-muted/30 px-3 py-2\">\n <div>\n <div className=\"text-[9px] font-semibold uppercase tracking-wider text-muted-foreground/70\">\n Conversion\n </div>\n <div className=\"text-xs font-bold text-emerald-600\">\n {data.metrics.conversion}\n </div>\n </div>\n <div className=\"border-l border-border pl-2\">\n <div className=\"text-[9px] font-semibold uppercase tracking-wider text-muted-foreground/70\">\n Median\n </div>\n <div className=\"text-xs font-bold text-foreground\">\n {data.metrics.medianTime}\n </div>\n </div>\n <div className=\"border-l border-border pl-2\">\n <div className=\"text-[9px] font-semibold uppercase tracking-wider text-muted-foreground/70\">\n Average\n </div>\n <div className=\"text-xs font-bold text-foreground\">\n {data.metrics.avgTime}\n </div>\n </div>\n </div>\n ) : null}\n\n {data.dropOffs && data.dropOffs.length > 0 ? (\n <div className=\"p-2\">\n <div className=\"mb-1.5 px-1 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground\">\n Drop-off Reasons\n </div>\n <div className=\"space-y-0.5\">\n {data.dropOffs.map((drop, i) => (\n <div\n key={i}\n onClick={(e) => {\n e.stopPropagation()\n onDropOffClick?.(drop.reason)\n }}\n className=\"group flex cursor-pointer items-center justify-between rounded p-1.5 text-xs transition-colors hover:bg-muted\"\n >\n <span className=\"truncate pr-2 text-muted-foreground group-hover:text-foreground\">\n {drop.reason}\n </span>\n <div className=\"flex items-center gap-1.5\">\n {drop.pct ? (\n <span className=\"text-[10px] text-muted-foreground/70\">\n {drop.pct}\n </span>\n ) : null}\n <span className=\"inline-flex h-4 min-w-[20px] items-center justify-center rounded border border-border bg-muted px-1 text-[9px] font-semibold text-muted-foreground group-hover:bg-muted group-hover:text-foreground\">\n {drop.count}\n </span>\n </div>\n </div>\n ))}\n </div>\n </div>\n ) : null}\n\n {onViewDetails ? (\n <div\n onClick={(e) => {\n e.stopPropagation()\n onViewDetails()\n }}\n className=\"flex cursor-pointer items-center justify-between border-t border-border bg-muted px-3 py-2 text-xs font-medium text-blue-600 transition-colors hover:bg-muted hover:text-blue-700\"\n >\n View Details\n <svg\n className=\"h-3 w-3\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n strokeWidth={2}\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n d=\"M9 5l7 7-7 7\"\n />\n </svg>\n </div>\n ) : null}\n </div>\n )\n}\n\nexport function SankeyChart({\n data,\n height = 400,\n nodeOpacity = 1,\n nodeThickness = 18,\n nodeBorderWidth = 0,\n linkOpacity = 0.25,\n linkHoverOpacity = 0.5,\n enableLabels = true,\n labelTextColor = \"#334155\",\n stageMetrics,\n onDropOffClick,\n onViewDetails,\n className,\n}: SankeyChartProps) {\n const [hoveredNode, setHoveredNode] = React.useState<HoveredNodeState | null>(\n null,\n )\n const containerRef = React.useRef<HTMLDivElement>(null)\n\n const handleNodeHover = React.useCallback(\n (node: { id: string; value?: number }, event: React.MouseEvent) => {\n if (hoveredNode?.pinned) return\n\n const containerRect = containerRef.current?.getBoundingClientRect()\n if (!containerRect) return\n\n const nodeId = node.id as string\n const stageKey = nodeId.toLowerCase().replace(/[^a-z]/g, \"\")\n const meta = stageMetrics?.[stageKey] || stageMetrics?.[nodeId]\n\n const hoverData: SankeyHoverCardData = {\n title: nodeId,\n count: node.value ?? 0,\n metrics: meta?.metrics,\n dropOffs: meta?.dropOffs,\n }\n\n setHoveredNode({\n id: nodeId,\n x: event.clientX - containerRect.left,\n y: event.clientY - containerRect.top,\n data: hoverData,\n })\n },\n [hoveredNode?.pinned, stageMetrics],\n )\n\n const handleNodeLeave = React.useCallback(() => {\n if (!hoveredNode?.pinned) {\n setHoveredNode(null)\n }\n }, [hoveredNode?.pinned])\n\n const handleClick = React.useCallback(() => {\n if (hoveredNode?.pinned) {\n setHoveredNode(null)\n } else if (hoveredNode) {\n setHoveredNode((prev) => (prev ? { ...prev, pinned: true } : null))\n }\n }, [hoveredNode])\n\n return (\n <div\n ref={containerRef}\n className={cn(\"relative\", className)}\n style={{ height }}\n onClick={handleClick}\n >\n <ResponsiveSankey\n data={data}\n margin={{ top: 20, right: 160, bottom: 20, left: 20 }}\n align=\"justify\"\n colors={(node: { nodeColor?: string }) => node.nodeColor || \"#94a3b8\"}\n nodeOpacity={nodeOpacity}\n nodeHoverOpacity={1}\n nodeThickness={nodeThickness}\n nodeSpacing={24}\n nodeBorderWidth={nodeBorderWidth}\n nodeBorderColor={{ from: \"color\", modifiers: [[\"darker\", 0.8]] }}\n nodeBorderRadius={3}\n linkOpacity={linkOpacity}\n linkHoverOpacity={linkHoverOpacity}\n linkHoverOthersOpacity={0.1}\n linkContract={3}\n enableLinkGradient\n labelPosition=\"outside\"\n labelOrientation=\"horizontal\"\n labelPadding={16}\n labelTextColor={labelTextColor}\n enableLabels={enableLabels}\n animate\n {...({ onNodeMouseEnter: handleNodeHover, onNodeMouseLeave: handleNodeLeave } as Record<string, unknown>)}\n />\n\n {hoveredNode ? (\n <HoverCard\n data={hoveredNode.data}\n position={{ x: hoveredNode.x, y: hoveredNode.y }}\n onDropOffClick={onDropOffClick}\n onViewDetails={\n onViewDetails\n ? () => onViewDetails(hoveredNode.id)\n : undefined\n }\n />\n ) : null}\n </div>\n )\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
interface MetricCardData {
|
|
4
|
+
label: string;
|
|
5
|
+
value: string | number;
|
|
6
|
+
trend?: {
|
|
7
|
+
value: number;
|
|
8
|
+
direction: "up" | "down" | "neutral";
|
|
9
|
+
};
|
|
10
|
+
alert?: boolean;
|
|
11
|
+
}
|
|
12
|
+
interface TopLineMetricsProps {
|
|
13
|
+
metrics: MetricCardData[];
|
|
14
|
+
chartData?: {
|
|
15
|
+
date: string;
|
|
16
|
+
value: number;
|
|
17
|
+
secondary?: number;
|
|
18
|
+
}[];
|
|
19
|
+
filters?: string[];
|
|
20
|
+
activeFilter?: string;
|
|
21
|
+
onFilterChange?: (filter: string) => void;
|
|
22
|
+
className?: string;
|
|
23
|
+
}
|
|
24
|
+
declare function TopLineMetrics({ metrics, chartData, filters, activeFilter, onFilterChange, className, }: TopLineMetricsProps): React.JSX.Element;
|
|
25
|
+
|
|
26
|
+
export { type MetricCardData, TopLineMetrics, type TopLineMetricsProps };
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
"use client";
|
|
4
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
import {
|
|
7
|
+
AreaChart,
|
|
8
|
+
Area,
|
|
9
|
+
Tooltip,
|
|
10
|
+
ResponsiveContainer
|
|
11
|
+
} from "recharts";
|
|
12
|
+
import { ArrowUp, ArrowDown, MoreHorizontal } from "lucide-react";
|
|
13
|
+
import { cn } from "../lib/utils.js";
|
|
14
|
+
import { Button } from "../components/button.js";
|
|
15
|
+
function generateDefaultData() {
|
|
16
|
+
const data = [];
|
|
17
|
+
const base = 150;
|
|
18
|
+
for (let i = 0; i < 7; i++) {
|
|
19
|
+
const date = /* @__PURE__ */ new Date();
|
|
20
|
+
date.setDate(date.getDate() - (6 - i));
|
|
21
|
+
const dayFactor = Math.sin(i * 0.8) * 40;
|
|
22
|
+
const value = Math.floor(base + dayFactor + Math.random() * 30);
|
|
23
|
+
const secondary = Math.floor(value * 0.4);
|
|
24
|
+
data.push({
|
|
25
|
+
date: date.toLocaleDateString("en-US", {
|
|
26
|
+
month: "2-digit",
|
|
27
|
+
day: "2-digit"
|
|
28
|
+
}),
|
|
29
|
+
value,
|
|
30
|
+
secondary
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
return data;
|
|
34
|
+
}
|
|
35
|
+
const DEFAULT_DATA = generateDefaultData();
|
|
36
|
+
function TopLineMetrics({
|
|
37
|
+
metrics,
|
|
38
|
+
chartData,
|
|
39
|
+
filters,
|
|
40
|
+
activeFilter,
|
|
41
|
+
onFilterChange,
|
|
42
|
+
className
|
|
43
|
+
}) {
|
|
44
|
+
var _a, _b;
|
|
45
|
+
const [internalFilter, setInternalFilter] = React.useState(
|
|
46
|
+
(_a = activeFilter != null ? activeFilter : filters == null ? void 0 : filters[0]) != null ? _a : "All"
|
|
47
|
+
);
|
|
48
|
+
const currentFilter = activeFilter != null ? activeFilter : internalFilter;
|
|
49
|
+
const data = chartData != null ? chartData : DEFAULT_DATA;
|
|
50
|
+
const primaryMetric = metrics[0];
|
|
51
|
+
const handleFilterClick = (f) => {
|
|
52
|
+
setInternalFilter(f);
|
|
53
|
+
onFilterChange == null ? void 0 : onFilterChange(f);
|
|
54
|
+
};
|
|
55
|
+
return /* @__PURE__ */ jsx("div", { className: cn("w-full", className), children: /* @__PURE__ */ jsxs("div", { className: "overflow-hidden rounded-xl border border-border bg-card shadow-sm", children: [
|
|
56
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-col gap-4 border-b border-border p-6", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between", children: [
|
|
57
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
58
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-4 flex items-center gap-4", children: [
|
|
59
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold uppercase tracking-wide text-muted-foreground", children: "Work Queue Activity" }),
|
|
60
|
+
filters && filters.length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
61
|
+
/* @__PURE__ */ jsx("div", { className: "h-4 w-px bg-muted" }),
|
|
62
|
+
/* @__PURE__ */ jsx("div", { className: "flex gap-1", children: filters.map((f) => /* @__PURE__ */ jsx(
|
|
63
|
+
"button",
|
|
64
|
+
{
|
|
65
|
+
onClick: () => handleFilterClick(f),
|
|
66
|
+
className: cn(
|
|
67
|
+
"rounded-full px-2 py-0.5 text-xs font-medium transition-colors",
|
|
68
|
+
currentFilter === f ? "bg-muted text-foreground" : "text-muted-foreground hover:text-foreground"
|
|
69
|
+
),
|
|
70
|
+
children: f
|
|
71
|
+
},
|
|
72
|
+
f
|
|
73
|
+
)) })
|
|
74
|
+
] }) : null
|
|
75
|
+
] }),
|
|
76
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-3", children: [
|
|
77
|
+
/* @__PURE__ */ jsx("span", { className: "text-4xl font-bold tracking-tight text-foreground", children: primaryMetric.value }),
|
|
78
|
+
/* @__PURE__ */ jsx("span", { className: "text-lg font-medium text-muted-foreground", children: primaryMetric.label }),
|
|
79
|
+
primaryMetric.trend ? /* @__PURE__ */ jsxs(
|
|
80
|
+
"span",
|
|
81
|
+
{
|
|
82
|
+
className: cn(
|
|
83
|
+
"ml-2 flex items-center rounded-full px-2 py-0.5 text-sm font-bold",
|
|
84
|
+
primaryMetric.trend.direction === "up" ? "bg-emerald-50 text-emerald-700" : "bg-red-50 text-red-700"
|
|
85
|
+
),
|
|
86
|
+
children: [
|
|
87
|
+
primaryMetric.trend.direction === "up" ? /* @__PURE__ */ jsx(ArrowUp, { className: "mr-1 h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(ArrowDown, { className: "mr-1 h-3.5 w-3.5" }),
|
|
88
|
+
primaryMetric.trend.value,
|
|
89
|
+
"%"
|
|
90
|
+
]
|
|
91
|
+
}
|
|
92
|
+
) : null
|
|
93
|
+
] })
|
|
94
|
+
] }),
|
|
95
|
+
/* @__PURE__ */ jsx(
|
|
96
|
+
Button,
|
|
97
|
+
{
|
|
98
|
+
variant: "ghost",
|
|
99
|
+
size: "sm",
|
|
100
|
+
className: "h-8 w-8 p-0 text-muted-foreground/70 hover:text-muted-foreground",
|
|
101
|
+
children: /* @__PURE__ */ jsx(MoreHorizontal, { className: "h-5 w-5" })
|
|
102
|
+
}
|
|
103
|
+
)
|
|
104
|
+
] }) }),
|
|
105
|
+
/* @__PURE__ */ jsxs("div", { className: "group relative h-[240px] w-full", children: [
|
|
106
|
+
/* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxs(
|
|
107
|
+
AreaChart,
|
|
108
|
+
{
|
|
109
|
+
data,
|
|
110
|
+
margin: { top: 0, right: 0, left: 0, bottom: 0 },
|
|
111
|
+
children: [
|
|
112
|
+
/* @__PURE__ */ jsxs("defs", { children: [
|
|
113
|
+
/* @__PURE__ */ jsxs(
|
|
114
|
+
"linearGradient",
|
|
115
|
+
{
|
|
116
|
+
id: "tlm-primary",
|
|
117
|
+
x1: "0",
|
|
118
|
+
y1: "0",
|
|
119
|
+
x2: "0",
|
|
120
|
+
y2: "1",
|
|
121
|
+
children: [
|
|
122
|
+
/* @__PURE__ */ jsx("stop", { offset: "5%", stopColor: "#10b981", stopOpacity: 0.3 }),
|
|
123
|
+
/* @__PURE__ */ jsx("stop", { offset: "95%", stopColor: "#10b981", stopOpacity: 0 })
|
|
124
|
+
]
|
|
125
|
+
}
|
|
126
|
+
),
|
|
127
|
+
/* @__PURE__ */ jsxs(
|
|
128
|
+
"linearGradient",
|
|
129
|
+
{
|
|
130
|
+
id: "tlm-secondary",
|
|
131
|
+
x1: "0",
|
|
132
|
+
y1: "0",
|
|
133
|
+
x2: "0",
|
|
134
|
+
y2: "1",
|
|
135
|
+
children: [
|
|
136
|
+
/* @__PURE__ */ jsx("stop", { offset: "5%", stopColor: "#f97316", stopOpacity: 0.3 }),
|
|
137
|
+
/* @__PURE__ */ jsx("stop", { offset: "95%", stopColor: "#f97316", stopOpacity: 0 })
|
|
138
|
+
]
|
|
139
|
+
}
|
|
140
|
+
)
|
|
141
|
+
] }),
|
|
142
|
+
/* @__PURE__ */ jsx(
|
|
143
|
+
Tooltip,
|
|
144
|
+
{
|
|
145
|
+
content: ({ active, payload }) => {
|
|
146
|
+
if (!active || !(payload == null ? void 0 : payload.length)) return null;
|
|
147
|
+
return /* @__PURE__ */ jsxs("div", { className: "min-w-[200px] animate-in fade-in zoom-in-95 rounded-lg border border-border bg-card/95 p-4 shadow-lg backdrop-blur-sm duration-200", children: [
|
|
148
|
+
/* @__PURE__ */ jsx("div", { className: "mb-2 text-xs font-semibold uppercase tracking-wider text-muted-foreground/70", children: "Metrics Snapshot" }),
|
|
149
|
+
/* @__PURE__ */ jsx("div", { className: "space-y-3", children: metrics.map((m, i) => /* @__PURE__ */ jsxs(
|
|
150
|
+
"div",
|
|
151
|
+
{
|
|
152
|
+
className: "flex items-center justify-between gap-4",
|
|
153
|
+
children: [
|
|
154
|
+
/* @__PURE__ */ jsx(
|
|
155
|
+
"span",
|
|
156
|
+
{
|
|
157
|
+
className: cn(
|
|
158
|
+
"text-sm font-medium",
|
|
159
|
+
i === 0 ? "font-bold text-foreground" : "text-muted-foreground"
|
|
160
|
+
),
|
|
161
|
+
children: m.label
|
|
162
|
+
}
|
|
163
|
+
),
|
|
164
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
165
|
+
/* @__PURE__ */ jsx(
|
|
166
|
+
"span",
|
|
167
|
+
{
|
|
168
|
+
className: cn(
|
|
169
|
+
"font-mono text-sm",
|
|
170
|
+
m.alert ? "font-bold text-red-600" : "text-foreground"
|
|
171
|
+
),
|
|
172
|
+
children: m.value
|
|
173
|
+
}
|
|
174
|
+
),
|
|
175
|
+
m.alert ? /* @__PURE__ */ jsx("div", { className: "h-1.5 w-1.5 animate-pulse rounded-full bg-red-500" }) : null
|
|
176
|
+
] })
|
|
177
|
+
]
|
|
178
|
+
},
|
|
179
|
+
i
|
|
180
|
+
)) })
|
|
181
|
+
] });
|
|
182
|
+
},
|
|
183
|
+
cursor: {
|
|
184
|
+
stroke: "#94a3b8",
|
|
185
|
+
strokeWidth: 1,
|
|
186
|
+
strokeDasharray: "4 4"
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
),
|
|
190
|
+
/* @__PURE__ */ jsx(
|
|
191
|
+
Area,
|
|
192
|
+
{
|
|
193
|
+
type: "monotone",
|
|
194
|
+
dataKey: "value",
|
|
195
|
+
stroke: "#10b981",
|
|
196
|
+
strokeWidth: 3,
|
|
197
|
+
fillOpacity: 1,
|
|
198
|
+
fill: "url(#tlm-primary)"
|
|
199
|
+
}
|
|
200
|
+
),
|
|
201
|
+
((_b = data[0]) == null ? void 0 : _b.secondary) != null ? /* @__PURE__ */ jsx(
|
|
202
|
+
Area,
|
|
203
|
+
{
|
|
204
|
+
type: "monotone",
|
|
205
|
+
dataKey: "secondary",
|
|
206
|
+
stroke: "#f97316",
|
|
207
|
+
strokeWidth: 3,
|
|
208
|
+
fillOpacity: 1,
|
|
209
|
+
fill: "url(#tlm-secondary)",
|
|
210
|
+
style: { opacity: 0.6 }
|
|
211
|
+
}
|
|
212
|
+
) : null
|
|
213
|
+
]
|
|
214
|
+
}
|
|
215
|
+
) }),
|
|
216
|
+
/* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute bottom-4 left-0 right-0 flex justify-between px-8 text-xs font-medium text-muted-foreground/70", children: data.map((d, i) => /* @__PURE__ */ jsx("span", { children: d.date }, i)) }),
|
|
217
|
+
/* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute right-4 top-4 rounded bg-background/50 px-2 py-1 text-[10px] text-muted-foreground opacity-0 backdrop-blur-sm transition-opacity group-hover:opacity-100", children: "Hover for details" })
|
|
218
|
+
] })
|
|
219
|
+
] }) });
|
|
220
|
+
}
|
|
221
|
+
export {
|
|
222
|
+
TopLineMetrics
|
|
223
|
+
};
|
|
224
|
+
//# sourceMappingURL=top-line-metrics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/charts/top-line-metrics.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport {\n AreaChart,\n Area,\n Tooltip,\n ResponsiveContainer,\n} from \"recharts\"\nimport { ArrowUp, ArrowDown, MoreHorizontal } from \"lucide-react\"\n\nimport { cn } from \"../lib/utils\"\nimport { Button } from \"../components/button\"\n\nexport interface MetricCardData {\n label: string\n value: string | number\n trend?: {\n value: number\n direction: \"up\" | \"down\" | \"neutral\"\n }\n alert?: boolean\n}\n\nexport interface TopLineMetricsProps {\n metrics: MetricCardData[]\n chartData?: { date: string; value: number; secondary?: number }[]\n filters?: string[]\n activeFilter?: string\n onFilterChange?: (filter: string) => void\n className?: string\n}\n\nfunction generateDefaultData() {\n const data = []\n const base = 150\n for (let i = 0; i < 7; i++) {\n const date = new Date()\n date.setDate(date.getDate() - (6 - i))\n const dayFactor = Math.sin(i * 0.8) * 40\n const value = Math.floor(base + dayFactor + Math.random() * 30)\n const secondary = Math.floor(value * 0.4)\n data.push({\n date: date.toLocaleDateString(\"en-US\", {\n month: \"2-digit\",\n day: \"2-digit\",\n }),\n value,\n secondary,\n })\n }\n return data\n}\n\nconst DEFAULT_DATA = generateDefaultData()\n\nexport function TopLineMetrics({\n metrics,\n chartData,\n filters,\n activeFilter,\n onFilterChange,\n className,\n}: TopLineMetricsProps) {\n const [internalFilter, setInternalFilter] = React.useState(\n activeFilter ?? filters?.[0] ?? \"All\",\n )\n const currentFilter = activeFilter ?? internalFilter\n const data = chartData ?? DEFAULT_DATA\n const primaryMetric = metrics[0]\n\n const handleFilterClick = (f: string) => {\n setInternalFilter(f)\n onFilterChange?.(f)\n }\n\n return (\n <div className={cn(\"w-full\", className)}>\n <div className=\"overflow-hidden rounded-xl border border-border bg-card shadow-sm\">\n <div className=\"flex flex-col gap-4 border-b border-border p-6\">\n <div className=\"flex items-start justify-between\">\n <div>\n <div className=\"mb-4 flex items-center gap-4\">\n <h3 className=\"text-sm font-semibold uppercase tracking-wide text-muted-foreground\">\n Work Queue Activity\n </h3>\n {filters && filters.length > 0 ? (\n <>\n <div className=\"h-4 w-px bg-muted\" />\n <div className=\"flex gap-1\">\n {filters.map((f) => (\n <button\n key={f}\n onClick={() => handleFilterClick(f)}\n className={cn(\n \"rounded-full px-2 py-0.5 text-xs font-medium transition-colors\",\n currentFilter === f\n ? \"bg-muted text-foreground\"\n : \"text-muted-foreground hover:text-foreground\",\n )}\n >\n {f}\n </button>\n ))}\n </div>\n </>\n ) : null}\n </div>\n\n <div className=\"flex items-baseline gap-3\">\n <span className=\"text-4xl font-bold tracking-tight text-foreground\">\n {primaryMetric.value}\n </span>\n <span className=\"text-lg font-medium text-muted-foreground\">\n {primaryMetric.label}\n </span>\n {primaryMetric.trend ? (\n <span\n className={cn(\n \"ml-2 flex items-center rounded-full px-2 py-0.5 text-sm font-bold\",\n primaryMetric.trend.direction === \"up\"\n ? \"bg-emerald-50 text-emerald-700\"\n : \"bg-red-50 text-red-700\",\n )}\n >\n {primaryMetric.trend.direction === \"up\" ? (\n <ArrowUp className=\"mr-1 h-3.5 w-3.5\" />\n ) : (\n <ArrowDown className=\"mr-1 h-3.5 w-3.5\" />\n )}\n {primaryMetric.trend.value}%\n </span>\n ) : null}\n </div>\n </div>\n\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-8 w-8 p-0 text-muted-foreground/70 hover:text-muted-foreground\"\n >\n <MoreHorizontal className=\"h-5 w-5\" />\n </Button>\n </div>\n </div>\n\n <div className=\"group relative h-[240px] w-full\">\n <ResponsiveContainer width=\"100%\" height=\"100%\">\n <AreaChart\n data={data}\n margin={{ top: 0, right: 0, left: 0, bottom: 0 }}\n >\n <defs>\n <linearGradient\n id=\"tlm-primary\"\n x1=\"0\"\n y1=\"0\"\n x2=\"0\"\n y2=\"1\"\n >\n <stop offset=\"5%\" stopColor=\"#10b981\" stopOpacity={0.3} />\n <stop offset=\"95%\" stopColor=\"#10b981\" stopOpacity={0} />\n </linearGradient>\n <linearGradient\n id=\"tlm-secondary\"\n x1=\"0\"\n y1=\"0\"\n x2=\"0\"\n y2=\"1\"\n >\n <stop offset=\"5%\" stopColor=\"#f97316\" stopOpacity={0.3} />\n <stop offset=\"95%\" stopColor=\"#f97316\" stopOpacity={0} />\n </linearGradient>\n </defs>\n <Tooltip\n content={({ active, payload }) => {\n if (!active || !payload?.length) return null\n return (\n <div className=\"min-w-[200px] animate-in fade-in zoom-in-95 rounded-lg border border-border bg-card/95 p-4 shadow-lg backdrop-blur-sm duration-200\">\n <div className=\"mb-2 text-xs font-semibold uppercase tracking-wider text-muted-foreground/70\">\n Metrics Snapshot\n </div>\n <div className=\"space-y-3\">\n {metrics.map((m, i) => (\n <div\n key={i}\n className=\"flex items-center justify-between gap-4\"\n >\n <span\n className={cn(\n \"text-sm font-medium\",\n i === 0\n ? \"font-bold text-foreground\"\n : \"text-muted-foreground\",\n )}\n >\n {m.label}\n </span>\n <div className=\"flex items-center gap-2\">\n <span\n className={cn(\n \"font-mono text-sm\",\n m.alert\n ? \"font-bold text-red-600\"\n : \"text-foreground\",\n )}\n >\n {m.value}\n </span>\n {m.alert ? (\n <div className=\"h-1.5 w-1.5 animate-pulse rounded-full bg-red-500\" />\n ) : null}\n </div>\n </div>\n ))}\n </div>\n </div>\n )\n }}\n cursor={{\n stroke: \"#94a3b8\",\n strokeWidth: 1,\n strokeDasharray: \"4 4\",\n }}\n />\n <Area\n type=\"monotone\"\n dataKey=\"value\"\n stroke=\"#10b981\"\n strokeWidth={3}\n fillOpacity={1}\n fill=\"url(#tlm-primary)\"\n />\n {data[0]?.secondary != null ? (\n <Area\n type=\"monotone\"\n dataKey=\"secondary\"\n stroke=\"#f97316\"\n strokeWidth={3}\n fillOpacity={1}\n fill=\"url(#tlm-secondary)\"\n style={{ opacity: 0.6 }}\n />\n ) : null}\n </AreaChart>\n </ResponsiveContainer>\n\n <div className=\"pointer-events-none absolute bottom-4 left-0 right-0 flex justify-between px-8 text-xs font-medium text-muted-foreground/70\">\n {data.map((d, i) => (\n <span key={i}>{d.date}</span>\n ))}\n </div>\n\n <div className=\"pointer-events-none absolute right-4 top-4 rounded bg-background/50 px-2 py-1 text-[10px] text-muted-foreground opacity-0 backdrop-blur-sm transition-opacity group-hover:opacity-100\">\n Hover for details\n </div>\n </div>\n </div>\n </div>\n )\n}\n"],"mappings":"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","names":[]}
|