@littlebearapps/platform-admin-sdk 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/README.md +2 -5
  2. package/dist/templates.d.ts +1 -1
  3. package/dist/templates.js +121 -3
  4. package/package.json +1 -1
  5. package/templates/full/dashboard/src/components/notifications/NotificationDropdown.tsx +130 -0
  6. package/templates/full/dashboard/src/components/notifications/NotificationItem.tsx +264 -0
  7. package/templates/full/dashboard/src/components/patterns/PatternInfoButton.tsx +60 -0
  8. package/templates/full/dashboard/src/components/reports/FeatureUsageReport.tsx +339 -0
  9. package/templates/full/dashboard/src/components/search/SearchResultGroup.tsx +46 -0
  10. package/templates/full/dashboard/src/components/search/SearchResultItem.tsx +212 -0
  11. package/templates/full/dashboard/src/pages/api/patterns/[id]/approve.ts +49 -0
  12. package/templates/full/dashboard/src/pages/api/patterns/[id]/reject.ts +50 -0
  13. package/templates/full/dashboard/src/pages/api/reports/digests/stats.ts +38 -0
  14. package/templates/full/dashboard/src/pages/api/reports/digests.ts +39 -0
  15. package/templates/full/dashboard/src/pages/api/search/reindex/[type].ts +56 -0
  16. package/templates/full/dashboard/src/pages/api/test-reports/[id].ts +102 -0
  17. package/templates/full/dashboard/src/pages/feedback.astro +365 -0
  18. package/templates/full/dashboard/src/pages/kiosk.astro +206 -0
  19. package/templates/full/dashboard/src/pages/map.astro +561 -0
  20. package/templates/full/dashboard/src/pages/revenue.astro +72 -0
  21. package/templates/full/dashboard/src/pages/tests.astro +431 -0
  22. package/templates/full/scripts/ops/audit-cost-anomaly.ts +430 -0
  23. package/templates/full/scripts/ops/verify-account-total.ts +256 -0
  24. package/templates/full/tests/integration/feedback-schema.test.ts +361 -0
  25. package/templates/full/tests/integration/r2-archive.test.ts +108 -0
  26. package/templates/shared/.github/workflows/dependabot-automerge.yml +41 -0
  27. package/templates/shared/.github/workflows/validate-controls.yml +27 -0
  28. package/templates/shared/dashboard/src/components/Breadcrumbs.astro +101 -0
  29. package/templates/shared/dashboard/src/components/EmptyState.astro +46 -0
  30. package/templates/shared/dashboard/src/components/ErrorBoundary.astro +79 -0
  31. package/templates/shared/dashboard/src/components/LoadingSkeleton.astro +105 -0
  32. package/templates/shared/dashboard/src/components/PageShell.astro +72 -0
  33. package/templates/shared/dashboard/src/components/SkipLinks.astro +22 -0
  34. package/templates/shared/dashboard/src/components/Toast.astro +170 -0
  35. package/templates/shared/dashboard/src/components/ToastContainer.astro +156 -0
  36. package/templates/shared/dashboard/src/components/costs/ProviderCostsGrid.tsx +401 -0
  37. package/templates/shared/dashboard/src/components/costs/index.ts +4 -0
  38. package/templates/shared/dashboard/src/components/overview/AlertBanner.tsx +94 -0
  39. package/templates/shared/dashboard/src/components/overview/index.ts +9 -0
  40. package/templates/shared/dashboard/src/components/resources/CostChart.tsx +170 -0
  41. package/templates/shared/dashboard/src/components/resources/ProviderCard.tsx +272 -0
  42. package/templates/shared/dashboard/src/components/resources/ProviderDetail.tsx +293 -0
  43. package/templates/shared/dashboard/src/components/settings/SettingsCard.astro +102 -0
  44. package/templates/shared/dashboard/src/components/usage/AllowanceGauge.astro +170 -0
  45. package/templates/shared/dashboard/src/components/usage/AnomalyAlerts.astro +633 -0
  46. package/templates/shared/dashboard/src/components/usage/BillingCycleCountdown.astro +192 -0
  47. package/templates/shared/dashboard/src/components/usage/BurnRateHero.astro +539 -0
  48. package/templates/shared/dashboard/src/components/usage/CircuitBreakerEventLog.astro +542 -0
  49. package/templates/shared/dashboard/src/components/usage/CircuitBreakerPanel.tsx +292 -0
  50. package/templates/shared/dashboard/src/components/usage/CircuitBreakerStatus.astro +669 -0
  51. package/templates/shared/dashboard/src/components/usage/CompactThresholdBanner.astro +531 -0
  52. package/templates/shared/dashboard/src/components/usage/ComparisonModeSelector.astro +651 -0
  53. package/templates/shared/dashboard/src/components/usage/CostBreakdownChart.astro +381 -0
  54. package/templates/shared/dashboard/src/components/usage/CostBreakdownTable.astro +210 -0
  55. package/templates/shared/dashboard/src/components/usage/CostDataTable.astro +0 -0
  56. package/templates/shared/dashboard/src/components/usage/CostDonutChart.astro +311 -0
  57. package/templates/shared/dashboard/src/components/usage/DailyCostChart.astro +632 -0
  58. package/templates/shared/dashboard/src/components/usage/ExportButton.astro +114 -0
  59. package/templates/shared/dashboard/src/components/usage/FeatureBudgetsTable.astro +872 -0
  60. package/templates/shared/dashboard/src/components/usage/FilterBar.astro +190 -0
  61. package/templates/shared/dashboard/src/components/usage/FilterToggles.astro +175 -0
  62. package/templates/shared/dashboard/src/components/usage/GitHubUsageCard.astro +537 -0
  63. package/templates/shared/dashboard/src/components/usage/OverageCostCard.astro +212 -0
  64. package/templates/shared/dashboard/src/components/usage/PlanUtilizationCard.astro +193 -0
  65. package/templates/shared/dashboard/src/components/usage/ProjectCard.astro +640 -0
  66. package/templates/shared/dashboard/src/components/usage/ProjectCardsGrid.astro +272 -0
  67. package/templates/shared/dashboard/src/components/usage/ResourceSearch.astro +279 -0
  68. package/templates/shared/dashboard/src/components/usage/ServiceUtilizationList.astro +604 -0
  69. package/templates/shared/dashboard/src/components/usage/SparklineCard.astro +399 -0
  70. package/templates/shared/dashboard/src/components/usage/StatsHero.astro +600 -0
  71. package/templates/shared/dashboard/src/components/usage/TableFilters.astro +1033 -0
  72. package/templates/shared/dashboard/src/components/usage/ThresholdAlert.astro +271 -0
  73. package/templates/shared/dashboard/src/components/usage/ThresholdSettings.astro +618 -0
  74. package/templates/shared/dashboard/src/components/usage/TopSpenderCard.astro +170 -0
  75. package/templates/shared/dashboard/src/components/usage/UnifiedResourceTable.astro +1737 -0
  76. package/templates/shared/dashboard/src/components/usage/UsageCard.astro +135 -0
  77. package/templates/shared/dashboard/src/components/usage/UsageHealthBanner.astro +387 -0
  78. package/templates/shared/dashboard/src/components/usage/UtilizationBar.astro +159 -0
  79. package/templates/shared/dashboard/src/components/usage/WorkersBreakdownTable.astro +659 -0
  80. package/templates/shared/dashboard/src/components/usage/daily/CostChart.astro +461 -0
  81. package/templates/shared/dashboard/src/components/usage/daily/CostTable.astro +946 -0
  82. package/templates/shared/dashboard/src/components/usage/daily/DailyOverview.astro +1079 -0
  83. package/templates/shared/dashboard/src/components/usage/design-tokens.ts +187 -0
  84. package/templates/shared/dashboard/src/components/usage/filters/InlineDateRange.astro +285 -0
  85. package/templates/shared/dashboard/src/components/usage/filters/PeriodButtons.astro +157 -0
  86. package/templates/shared/dashboard/src/components/usage/filters/ProjectSelect.astro +284 -0
  87. package/templates/shared/dashboard/src/components/usage/scripts/ai-tab-controller.ts +419 -0
  88. package/templates/shared/dashboard/src/components/usage/scripts/constants.ts +60 -0
  89. package/templates/shared/dashboard/src/components/usage/scripts/formatters.ts +62 -0
  90. package/templates/shared/dashboard/src/components/usage/scripts/overview-controller.ts +1633 -0
  91. package/templates/shared/dashboard/src/components/usage/scripts/resource-table-builder.ts +294 -0
  92. package/templates/shared/dashboard/src/components/usage/scripts/tabs-filters-controller.ts +464 -0
  93. package/templates/shared/dashboard/src/components/usage/state/index.ts +55 -0
  94. package/templates/shared/dashboard/src/components/usage/state/usageActions.ts +439 -0
  95. package/templates/shared/dashboard/src/components/usage/state/usageStore.ts +376 -0
  96. package/templates/shared/dashboard/src/components/usage/types.ts +283 -0
  97. package/templates/shared/dashboard/src/components/usage/usage-colors.ts +292 -0
  98. package/templates/shared/dashboard/src/pages/api/usage/ai-models.ts +235 -0
  99. package/templates/shared/dashboard/src/pages/api/usage/billing-context.ts +296 -0
  100. package/templates/shared/scripts/test-telemetry-flow.ts +464 -0
  101. package/templates/shared/tests/e2e/usage-export.test.ts +784 -0
  102. package/templates/shared/tests/e2e/usage-mobile.test.ts +531 -0
  103. package/templates/standard/dashboard/src/components/errors/PriorityBadge.astro +27 -0
  104. package/templates/standard/dashboard/src/components/infrastructure/HealthchecksStatus.tsx +293 -0
  105. package/templates/standard/dashboard/src/components/infrastructure/InfrastructureTabs.tsx +268 -0
  106. package/templates/standard/dashboard/src/pages/analytics.astro +64 -0
  107. package/templates/standard/dashboard/src/pages/api/infrastructure/alerts.ts +85 -0
  108. package/templates/standard/dashboard/src/pages/api/infrastructure/healthchecks/[id]/flips.ts +110 -0
  109. package/templates/standard/dashboard/src/pages/api/infrastructure/healthchecks.ts +101 -0
  110. package/templates/standard/dashboard/src/pages/api/infrastructure/uptime/[id]/response-times.ts +121 -0
  111. package/templates/standard/dashboard/src/pages/api/infrastructure/uptime.ts +89 -0
  112. package/templates/standard/dashboard/src/pages/api/test/service-auth.ts +178 -0
  113. package/templates/standard/tests/integration/connectors.test.ts +241 -0
  114. package/templates/standard/tests/integration/github-monitor.test.ts +143 -0
  115. package/templates/standard/tests/integration/ingestion.test.ts +211 -0
@@ -0,0 +1,461 @@
1
+ ---
2
+ /**
3
+ * CostChart.astro
4
+ *
5
+ * Stacked bar chart for daily cost breakdown.
6
+ * Subscribes to nanostores for reactive data updates.
7
+ * Part of task-20.4: Phase 2.2 - Create CostChart.astro (simplified)
8
+ *
9
+ * @example
10
+ * ```astro
11
+ * <CostChart />
12
+ * ```
13
+ */
14
+
15
+ // Generate unique chart ID for canvas reference
16
+ const chartId = `cost-chart-${Math.random().toString(36).slice(2, 9)}`;
17
+ ---
18
+
19
+ <div class="cost-chart" data-component="cost-chart" data-chart-id={chartId}>
20
+ <div class="chart-wrapper">
21
+ <canvas id={chartId} class="chart-canvas"></canvas>
22
+ </div>
23
+
24
+ <!-- Custom legend -->
25
+ <div class="chart-legend" id={`${chartId}-legend`}>
26
+ <div class="legend-row">
27
+ <span class="legend-item" data-resource="workers">
28
+ <span class="legend-dot legend-dot--workers"></span>
29
+ <span class="legend-label">Workers</span>
30
+ </span>
31
+ <span class="legend-item" data-resource="d1">
32
+ <span class="legend-dot legend-dot--d1"></span>
33
+ <span class="legend-label">D1</span>
34
+ </span>
35
+ <span class="legend-item" data-resource="kv">
36
+ <span class="legend-dot legend-dot--kv"></span>
37
+ <span class="legend-label">KV</span>
38
+ </span>
39
+ <span class="legend-item" data-resource="r2">
40
+ <span class="legend-dot legend-dot--r2"></span>
41
+ <span class="legend-label">R2</span>
42
+ </span>
43
+ <span class="legend-item" data-resource="vectorize">
44
+ <span class="legend-dot legend-dot--vectorize"></span>
45
+ <span class="legend-label">Vectorize</span>
46
+ </span>
47
+ </div>
48
+ <div class="legend-row">
49
+ <span class="legend-item" data-resource="aiGateway">
50
+ <span class="legend-dot legend-dot--aiGateway"></span>
51
+ <span class="legend-label">AI Gateway</span>
52
+ </span>
53
+ <span class="legend-item" data-resource="durableObjects">
54
+ <span class="legend-dot legend-dot--durableObjects"></span>
55
+ <span class="legend-label">Durable Objects</span>
56
+ </span>
57
+ <span class="legend-item" data-resource="workersAI">
58
+ <span class="legend-dot legend-dot--workersAI"></span>
59
+ <span class="legend-label">Workers AI</span>
60
+ </span>
61
+ <span class="legend-item" data-resource="queues">
62
+ <span class="legend-dot legend-dot--queues"></span>
63
+ <span class="legend-label">Queues</span>
64
+ </span>
65
+ </div>
66
+ </div>
67
+ </div>
68
+
69
+ <style>
70
+ .cost-chart {
71
+ display: flex;
72
+ flex-direction: column;
73
+ height: 100%;
74
+ min-height: 280px;
75
+ }
76
+
77
+ .chart-wrapper {
78
+ flex: 1;
79
+ position: relative;
80
+ padding: var(--usage-spacing-md);
81
+ }
82
+
83
+ .chart-canvas {
84
+ width: 100% !important;
85
+ height: 100% !important;
86
+ }
87
+
88
+ /* Legend */
89
+ .chart-legend {
90
+ display: flex;
91
+ flex-direction: column;
92
+ gap: var(--usage-spacing-xs);
93
+ padding: var(--usage-spacing-sm) var(--usage-spacing-md);
94
+ border-top: 1px solid var(--usage-border-subtle);
95
+ background: var(--usage-bg-tertiary, var(--usage-bg-secondary));
96
+ }
97
+
98
+ .legend-row {
99
+ display: flex;
100
+ flex-wrap: wrap;
101
+ gap: var(--usage-spacing-sm) var(--usage-spacing-md);
102
+ justify-content: center;
103
+ }
104
+
105
+ .legend-item {
106
+ display: inline-flex;
107
+ align-items: center;
108
+ gap: 6px;
109
+ font-size: 0.75rem;
110
+ color: var(--usage-text-muted);
111
+ cursor: pointer;
112
+ transition: opacity 0.15s ease;
113
+ }
114
+
115
+ .legend-item:hover {
116
+ opacity: 0.8;
117
+ }
118
+
119
+ .legend-item[data-hidden='true'] {
120
+ opacity: 0.4;
121
+ }
122
+
123
+ .legend-item[data-hidden='true'] .legend-dot {
124
+ background: var(--usage-text-muted);
125
+ }
126
+
127
+ .legend-dot {
128
+ width: 10px;
129
+ height: 10px;
130
+ border-radius: 2px;
131
+ flex-shrink: 0;
132
+ }
133
+
134
+ .legend-dot--workers {
135
+ background: var(--usage-resource-workers, #f59e0b);
136
+ }
137
+ .legend-dot--d1 {
138
+ background: var(--usage-resource-d1, #3b82f6);
139
+ }
140
+ .legend-dot--kv {
141
+ background: var(--usage-resource-kv, #10b981);
142
+ }
143
+ .legend-dot--r2 {
144
+ background: var(--usage-resource-r2, #8b5cf6);
145
+ }
146
+ .legend-dot--vectorize {
147
+ background: var(--usage-resource-vectorize, #ec4899);
148
+ }
149
+ .legend-dot--aiGateway {
150
+ background: var(--usage-resource-aiGateway, #06b6d4);
151
+ }
152
+ .legend-dot--durableObjects {
153
+ background: var(--usage-resource-durableObjects, #f97316);
154
+ }
155
+ .legend-dot--workersAI {
156
+ background: var(--usage-resource-workersAI, #6366f1);
157
+ }
158
+ .legend-dot--queues {
159
+ background: var(--usage-resource-queues, #14b8a6);
160
+ }
161
+
162
+ .legend-label {
163
+ white-space: nowrap;
164
+ }
165
+
166
+ /* Empty state */
167
+ .chart-empty {
168
+ display: flex;
169
+ flex-direction: column;
170
+ align-items: center;
171
+ justify-content: center;
172
+ height: 100%;
173
+ min-height: 200px;
174
+ color: var(--usage-text-muted);
175
+ gap: var(--usage-spacing-sm);
176
+ }
177
+
178
+ .chart-empty-icon {
179
+ opacity: 0.4;
180
+ }
181
+
182
+ .chart-empty-text {
183
+ font-size: 0.875rem;
184
+ }
185
+
186
+ /* Dark mode overrides */
187
+ :global([data-theme='dark']) .cost-chart,
188
+ :global(.dark) .cost-chart {
189
+ background: #21262d;
190
+ }
191
+
192
+ :global([data-theme='dark']) .chart-wrapper,
193
+ :global(.dark) .chart-wrapper {
194
+ background: #21262d;
195
+ }
196
+
197
+ :global([data-theme='dark']) .chart-legend,
198
+ :global(.dark) .chart-legend {
199
+ background: #161b22;
200
+ border-color: #21262d;
201
+ }
202
+
203
+ :global([data-theme='dark']) .legend-item,
204
+ :global(.dark) .legend-item {
205
+ color: #8b949e;
206
+ }
207
+
208
+ :global([data-theme='dark']) .chart-empty,
209
+ :global(.dark) .chart-empty {
210
+ color: #8b949e;
211
+ }
212
+ </style>
213
+
214
+ <script>
215
+ import { Chart, registerables } from 'chart.js';
216
+ import { $dailyData, $selectedDate, $timezone } from '../state/usageStore';
217
+ import { RESOURCE_INFO, getChartColors } from '../usage-colors';
218
+
219
+ // Register Chart.js components
220
+ Chart.register(...registerables);
221
+
222
+ // Get chartId from data attribute
223
+ const container = document.querySelector('[data-component="cost-chart"]');
224
+ const chartId = container?.dataset.chartId;
225
+ if (!chartId) {
226
+ console.error('CostChart: Missing chart ID');
227
+ }
228
+
229
+ // Resource keys in display order
230
+ const RESOURCE_KEYS = [
231
+ 'workers',
232
+ 'd1',
233
+ 'kv',
234
+ 'r2',
235
+ 'vectorize',
236
+ 'aiGateway',
237
+ 'durableObjects',
238
+ 'workersAI',
239
+ 'queues',
240
+ ];
241
+
242
+ // Melbourne date formatter
243
+ function formatDateLabel(dateStr, tz) {
244
+ const date = new Date(dateStr);
245
+ return date.toLocaleDateString('en-AU', {
246
+ timeZone: tz,
247
+ day: 'numeric',
248
+ month: 'short',
249
+ });
250
+ }
251
+
252
+ // Get current theme
253
+ function getTheme() {
254
+ return document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'light';
255
+ }
256
+
257
+ // Build datasets from daily data
258
+ function buildDatasets(data, theme) {
259
+ const colors = getChartColors(theme);
260
+
261
+ return RESOURCE_KEYS.map((key, index) => {
262
+ const info = RESOURCE_INFO.find((r) => r.key === key);
263
+ return {
264
+ label: info?.label || key,
265
+ data: data.map((d) => d[key] || 0),
266
+ backgroundColor: colors.backgroundColor[index],
267
+ hoverBackgroundColor: colors.hoverBackgroundColor[index],
268
+ borderRadius: 3,
269
+ borderSkipped: false,
270
+ };
271
+ });
272
+ }
273
+
274
+ // Chart instance
275
+ let chart = null;
276
+ const canvas = chartId ? document.getElementById(chartId) : null;
277
+
278
+ if (canvas) {
279
+ const ctx = canvas.getContext('2d');
280
+ const theme = getTheme();
281
+ const initialData = $dailyData.get();
282
+ const tz = $timezone.get();
283
+
284
+ // Initial chart config
285
+ chart = new Chart(ctx, {
286
+ type: 'bar',
287
+ data: {
288
+ labels: initialData.map((d) => formatDateLabel(d.date, tz)),
289
+ datasets: buildDatasets(initialData, theme),
290
+ },
291
+ options: {
292
+ responsive: true,
293
+ maintainAspectRatio: false,
294
+ interaction: {
295
+ mode: 'index',
296
+ intersect: false,
297
+ },
298
+ plugins: {
299
+ legend: {
300
+ display: false, // Using custom legend
301
+ },
302
+ tooltip: {
303
+ backgroundColor: theme === 'dark' ? '#1f2937' : '#ffffff',
304
+ titleColor: theme === 'dark' ? '#f9fafb' : '#111827',
305
+ bodyColor: theme === 'dark' ? '#d1d5db' : '#4b5563',
306
+ borderColor: theme === 'dark' ? '#374151' : '#e5e7eb',
307
+ borderWidth: 1,
308
+ padding: 12,
309
+ cornerRadius: 8,
310
+ displayColors: true,
311
+ boxWidth: 12,
312
+ boxHeight: 12,
313
+ boxPadding: 4,
314
+ callbacks: {
315
+ title: (items) => {
316
+ if (!items.length) return '';
317
+ const idx = items[0].dataIndex;
318
+ const data = $dailyData.get();
319
+ if (data[idx]) {
320
+ return formatDateLabel(data[idx].date, $timezone.get());
321
+ }
322
+ return items[0].label;
323
+ },
324
+ label: (item) => {
325
+ const value = item.raw || 0;
326
+ if (value === 0) return null; // Hide zero values
327
+ return ` ${item.dataset.label}: $${value.toFixed(4)}`;
328
+ },
329
+ footer: (items) => {
330
+ const total = items.reduce((sum, item) => sum + (item.raw || 0), 0);
331
+ return `Total: $${total.toFixed(2)}`;
332
+ },
333
+ },
334
+ },
335
+ },
336
+ scales: {
337
+ x: {
338
+ stacked: true,
339
+ grid: {
340
+ display: false,
341
+ },
342
+ ticks: {
343
+ color: theme === 'dark' ? '#9ca3af' : '#6b7280',
344
+ font: { size: 11 },
345
+ maxRotation: 45,
346
+ minRotation: 0,
347
+ },
348
+ border: {
349
+ display: false,
350
+ },
351
+ },
352
+ y: {
353
+ stacked: true,
354
+ beginAtZero: true,
355
+ grid: {
356
+ color: theme === 'dark' ? '#374151' : '#f3f4f6',
357
+ },
358
+ ticks: {
359
+ color: theme === 'dark' ? '#9ca3af' : '#6b7280',
360
+ font: { size: 11 },
361
+ callback: (value) => `$${value.toFixed(2)}`,
362
+ },
363
+ border: {
364
+ display: false,
365
+ },
366
+ },
367
+ },
368
+ onClick: (_event, elements) => {
369
+ if (elements.length > 0) {
370
+ const idx = elements[0].index;
371
+ const data = $dailyData.get();
372
+ if (data[idx]) {
373
+ const currentSelected = $selectedDate.get();
374
+ // Toggle selection
375
+ if (currentSelected === data[idx].date) {
376
+ $selectedDate.set(null);
377
+ } else {
378
+ $selectedDate.set(data[idx].date);
379
+ }
380
+ }
381
+ }
382
+ },
383
+ },
384
+ });
385
+
386
+ // Subscribe to data changes
387
+ $dailyData.subscribe((data) => {
388
+ if (!chart) return;
389
+
390
+ const tz = $timezone.get();
391
+ const theme = getTheme();
392
+
393
+ chart.data.labels = data.map((d) => formatDateLabel(d.date, tz));
394
+ chart.data.datasets = buildDatasets(data, theme);
395
+ chart.update('none'); // Skip animation for data updates
396
+ });
397
+
398
+ // Subscribe to selection changes - highlight selected bar
399
+ $selectedDate.subscribe((selectedDate) => {
400
+ if (!chart) return;
401
+
402
+ const data = $dailyData.get();
403
+ const selectedIndex = data.findIndex((d) => d.date === selectedDate);
404
+
405
+ // Update bar border to show selection
406
+ chart.data.datasets.forEach((dataset) => {
407
+ dataset.borderWidth = data.map((_, i) => (i === selectedIndex ? 2 : 0));
408
+ dataset.borderColor = data.map((_, i) =>
409
+ i === selectedIndex ? 'rgba(255, 255, 255, 0.8)' : 'transparent'
410
+ );
411
+ });
412
+
413
+ chart.update('none');
414
+ });
415
+
416
+ // Theme change observer
417
+ const observer = new MutationObserver(() => {
418
+ if (!chart) return;
419
+
420
+ const theme = getTheme();
421
+ const data = $dailyData.get();
422
+
423
+ // Update datasets with new theme colors
424
+ chart.data.datasets = buildDatasets(data, theme);
425
+
426
+ // Update tooltip colors
427
+ chart.options.plugins.tooltip.backgroundColor = theme === 'dark' ? '#1f2937' : '#ffffff';
428
+ chart.options.plugins.tooltip.titleColor = theme === 'dark' ? '#f9fafb' : '#111827';
429
+ chart.options.plugins.tooltip.bodyColor = theme === 'dark' ? '#d1d5db' : '#4b5563';
430
+ chart.options.plugins.tooltip.borderColor = theme === 'dark' ? '#374151' : '#e5e7eb';
431
+
432
+ // Update scale colors
433
+ chart.options.scales.x.ticks.color = theme === 'dark' ? '#9ca3af' : '#6b7280';
434
+ chart.options.scales.y.ticks.color = theme === 'dark' ? '#9ca3af' : '#6b7280';
435
+ chart.options.scales.y.grid.color = theme === 'dark' ? '#374151' : '#f3f4f6';
436
+
437
+ chart.update('none');
438
+ });
439
+
440
+ observer.observe(document.documentElement, {
441
+ attributes: true,
442
+ attributeFilter: ['data-theme'],
443
+ });
444
+
445
+ // Legend toggle handlers
446
+ const legendItems = document.querySelectorAll(`#${chartId}-legend .legend-item`);
447
+ legendItems.forEach((item) => {
448
+ item.addEventListener('click', () => {
449
+ const resource = item.getAttribute('data-resource');
450
+ const datasetIndex = RESOURCE_KEYS.indexOf(resource);
451
+
452
+ if (datasetIndex >= 0 && chart) {
453
+ const meta = chart.getDatasetMeta(datasetIndex);
454
+ meta.hidden = !meta.hidden;
455
+ item.setAttribute('data-hidden', String(meta.hidden));
456
+ chart.update();
457
+ }
458
+ });
459
+ });
460
+ }
461
+ </script>