feat: advanced analytics with deep linking, URL params, and progressive UI
All checks were successful
Deployment / deploy-docker (push) Successful in 16s
All checks were successful
Deployment / deploy-docker (push) Successful in 16s
This commit is contained in:
@@ -312,7 +312,12 @@
|
|||||||
const titles = { 'dashboard': 'Market Overview', 'analytics': 'Custom Report Builder', 'metadata': 'Entity Metadata' };
|
const titles = { 'dashboard': 'Market Overview', 'analytics': 'Custom Report Builder', 'metadata': 'Entity Metadata' };
|
||||||
document.getElementById('pageTitle').innerText = titles[viewId];
|
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() {
|
function handlePresetChange() {
|
||||||
@@ -323,12 +328,11 @@
|
|||||||
function updateSubGroupOptions() {
|
function updateSubGroupOptions() {
|
||||||
const x = document.getElementById('axisX').value;
|
const x = document.getElementById('axisX').value;
|
||||||
const sub = document.getElementById('axisSub');
|
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)
|
// Contextual Logic: If X is already a metadata field, don't allow it as series
|
||||||
container.classList.remove('hidden');
|
|
||||||
Array.from(sub.options).forEach(opt => {
|
Array.from(sub.options).forEach(opt => {
|
||||||
opt.disabled = (opt.value === x);
|
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)) {
|
if (!store.pinnedIsins.find(p => p.isin === isin)) {
|
||||||
store.pinnedIsins.push({ isin, name });
|
store.pinnedIsins.push({ isin, name });
|
||||||
updateFilterChips();
|
updateFilterChips();
|
||||||
// Close search
|
|
||||||
document.getElementById('isinSearch').value = '';
|
document.getElementById('isinSearch').value = '';
|
||||||
document.getElementById('suggestions').classList.add('hidden');
|
document.getElementById('suggestions').classList.add('hidden');
|
||||||
|
if (window.activeView === 'analytics') proceedToStep(5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -376,8 +380,8 @@
|
|||||||
${p.isin} <span class="ml-2 cursor-pointer text-slate-500 hover:text-white" onclick="removeFilter('${p.isin}')">×</span>
|
${p.isin} <span class="ml-2 cursor-pointer text-slate-500 hover:text-white" onclick="removeFilter('${p.isin}')">×</span>
|
||||||
</div>
|
</div>
|
||||||
`).join('');
|
`).join('');
|
||||||
container.innerHTML = html;
|
if (container) container.innerHTML = html;
|
||||||
pins.innerHTML = html;
|
if (pins) pins.innerHTML = html;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDates() {
|
function getDates() {
|
||||||
@@ -424,6 +428,9 @@
|
|||||||
const y = document.getElementById('axisY').value;
|
const y = document.getElementById('axisY').value;
|
||||||
const isins = store.pinnedIsins.map(p => p.isin).join(',');
|
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}`;
|
let url = `${API}/analytics?metric=${y}&group_by=${x}`;
|
||||||
if (sub) url += `&sub_group_by=${sub}`;
|
if (sub) url += `&sub_group_by=${sub}`;
|
||||||
if (dates.from) url += `&date_from=${dates.from}`;
|
if (dates.from) url += `&date_from=${dates.from}`;
|
||||||
@@ -504,7 +511,7 @@
|
|||||||
'#8b5cf6', // Purple
|
'#8b5cf6', // Purple
|
||||||
'#f97316', // Orange
|
'#f97316', // Orange
|
||||||
'#ec4899', // Pink
|
'#ec4899', // Pink
|
||||||
'#475569' // Slate
|
'#6366f1' // Indigo
|
||||||
];
|
];
|
||||||
|
|
||||||
charts.continent = new Chart(contCtx, {
|
charts.continent = new Chart(contCtx, {
|
||||||
@@ -523,12 +530,18 @@
|
|||||||
|
|
||||||
// --- Progressive Report Configuration ---
|
// --- Progressive Report Configuration ---
|
||||||
function proceedToStep(n) {
|
function proceedToStep(n) {
|
||||||
|
// First, enable/show the step
|
||||||
const step = document.getElementById(`step${n}`);
|
const step = document.getElementById(`step${n}`);
|
||||||
if (step) {
|
if (step) {
|
||||||
step.classList.remove('hidden');
|
step.classList.remove('hidden');
|
||||||
setTimeout(() => step.classList.remove('opacity-50'), 50);
|
setTimeout(() => step.classList.remove('opacity-50'), 50);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Contextual Visibility Logic: If we are at step 2, handle custom date toggles
|
||||||
if (n === 2) handlePresetChange();
|
if (n === 2) handlePresetChange();
|
||||||
|
|
||||||
|
// Auto-scroll logic if sidebar is long
|
||||||
|
if (n > 2) step.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkCustomDates() {
|
function checkCustomDates() {
|
||||||
@@ -552,7 +565,7 @@
|
|||||||
function fillMetadataTable() {
|
function fillMetadataTable() {
|
||||||
const tbody = document.getElementById('metadataRows');
|
const tbody = document.getElementById('metadataRows');
|
||||||
tbody.innerHTML = store.metadata.map(r => `
|
tbody.innerHTML = store.metadata.map(r => `
|
||||||
<tr class="hover:bg-white/5 transition border-b border-white/5 cursor-pointer" onclick="addFilter('${r[0]}', '${r[1]}'); showView('analytics');">
|
<tr class="hover:bg-white/5 transition border-b border-white/5 cursor-pointer" onclick="deepLinkToAnalytics('${r[0]}', '${r[1]}')">
|
||||||
<td class="p-5 px-8 font-mono text-sky-400 font-bold">${r[0]}</td>
|
<td class="p-5 px-8 font-mono text-sky-400 font-bold">${r[0]}</td>
|
||||||
<td class="p-5 font-semibold text-slate-200">${r[1]}</td>
|
<td class="p-5 font-semibold text-slate-200">${r[1]}</td>
|
||||||
<td class="p-5 text-slate-500">${r[2]}</td>
|
<td class="p-5 text-slate-500">${r[2]}</td>
|
||||||
@@ -561,12 +574,51 @@
|
|||||||
`).join('');
|
`).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) {
|
function filterMetadata(q) {
|
||||||
const rows = document.querySelectorAll('#metadataRows tr');
|
const rows = document.querySelectorAll('#metadataRows tr');
|
||||||
rows.forEach(r => r.style.display = r.innerText.toLowerCase().includes(q.toLowerCase()) ? '' : 'none');
|
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);
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user