From ecfdea5f6ca0f63a57646a4713ae96f9e66500f7 Mon Sep 17 00:00:00 2001 From: Melchior Reimers Date: Fri, 23 Jan 2026 19:33:39 +0100 Subject: [PATCH] feat: advanced analytics with deep linking, URL params, and progressive UI --- dashboard/public/index.html | 72 +++++++++++++++++++++++++++++++------ 1 file changed, 62 insertions(+), 10 deletions(-) diff --git a/dashboard/public/index.html b/dashboard/public/index.html index 56d9df2..8af042a 100644 --- a/dashboard/public/index.html +++ b/dashboard/public/index.html @@ -312,7 +312,12 @@ const titles = { 'dashboard': 'Market Overview', 'analytics': 'Custom Report Builder', 'metadata': 'Entity Metadata' }; document.getElementById('pageTitle').innerText = titles[viewId]; - if (viewId === 'analytics') renderAnalyticsReport(); else fetchData(); + if (viewId === 'analytics') { + // If it's a fresh visit without params, we might want to reset or keep state + // renderAnalyticsReport(); + } else { + fetchData(); + } } function handlePresetChange() { @@ -323,12 +328,11 @@ function updateSubGroupOptions() { const x = document.getElementById('axisX').value; const sub = document.getElementById('axisSub'); - const container = document.getElementById('subGroupContainer'); - // Contextual Logic: If X is already a metadata field, don't allow it as series (too complex) - container.classList.remove('hidden'); + // Contextual Logic: If X is already a metadata field, don't allow it as series Array.from(sub.options).forEach(opt => { opt.disabled = (opt.value === x); + if (opt.value === x && sub.value === x) sub.value = ''; }); } @@ -357,9 +361,9 @@ if (!store.pinnedIsins.find(p => p.isin === isin)) { store.pinnedIsins.push({ isin, name }); updateFilterChips(); - // Close search document.getElementById('isinSearch').value = ''; document.getElementById('suggestions').classList.add('hidden'); + if (window.activeView === 'analytics') proceedToStep(5); } } @@ -376,8 +380,8 @@ ${p.isin} × `).join(''); - container.innerHTML = html; - pins.innerHTML = html; + if (container) container.innerHTML = html; + if (pins) pins.innerHTML = html; } function getDates() { @@ -424,6 +428,9 @@ const y = document.getElementById('axisY').value; const isins = store.pinnedIsins.map(p => p.isin).join(','); + // Validate that basic steps are done + if (!dates.from || !x || !y) return; + let url = `${API}/analytics?metric=${y}&group_by=${x}`; if (sub) url += `&sub_group_by=${sub}`; if (dates.from) url += `&date_from=${dates.from}`; @@ -504,7 +511,7 @@ '#8b5cf6', // Purple '#f97316', // Orange '#ec4899', // Pink - '#475569' // Slate + '#6366f1' // Indigo ]; charts.continent = new Chart(contCtx, { @@ -523,12 +530,18 @@ // --- Progressive Report Configuration --- function proceedToStep(n) { + // First, enable/show the step const step = document.getElementById(`step${n}`); if (step) { step.classList.remove('hidden'); setTimeout(() => step.classList.remove('opacity-50'), 50); } + + // Contextual Visibility Logic: If we are at step 2, handle custom date toggles if (n === 2) handlePresetChange(); + + // Auto-scroll logic if sidebar is long + if (n > 2) step.scrollIntoView({ behavior: 'smooth', block: 'center' }); } function checkCustomDates() { @@ -552,7 +565,7 @@ function fillMetadataTable() { const tbody = document.getElementById('metadataRows'); tbody.innerHTML = store.metadata.map(r => ` - + ${r[0]} ${r[1]} ${r[2]} @@ -561,12 +574,51 @@ `).join(''); } + function deepLinkToAnalytics(isin, name) { + // Set values as requested: Time: 30d, X: Day, Y: Volume, Filter: ISIN + resetReportConfig(); + document.getElementById('timeRangePreset').value = '30'; + document.getElementById('axisX').value = 'day'; + document.getElementById('axisSub').value = ''; + document.getElementById('axisY').value = 'volume'; + + store.pinnedIsins = [{ isin, name }]; + updateFilterChips(); + + // Show all steps progressively for the user + for (let i = 1; i <= 5; i++) proceedToStep(i); + + showView('analytics'); + renderAnalyticsReport(); + } + + function handleUrlParams() { + const params = new URLSearchParams(window.location.search); + const view = params.get('view'); + const isin = params.get('isin'); + if (view) showView(view); + if (view === 'analytics' && isin) { + // If isin is provided in URL, we wait for metadata to be fetched then deep link + const checkStore = setInterval(() => { + if (store.metadata.length > 0) { + const entity = store.metadata.find(r => r[0] === isin); + if (entity) deepLinkToAnalytics(isin, entity[1]); + clearInterval(checkStore); + } + }, 500); + } + } + function filterMetadata(q) { const rows = document.querySelectorAll('#metadataRows tr'); rows.forEach(r => r.style.display = r.innerText.toLowerCase().includes(q.toLowerCase()) ? '' : 'none'); } - window.onload = () => { fetchData(); setInterval(fetchData, 30000); }; + window.onload = async () => { + await fetchData(); + handleUrlParams(); + setInterval(fetchData, 30000); + };