fixing dashboard
All checks were successful
Deployment / deploy-docker (push) Successful in 17s

This commit is contained in:
Melchior Reimers
2026-01-25 14:35:49 +01:00
parent 11805d0069
commit 15065336c3

View File

@@ -117,45 +117,55 @@
.report-step { .report-step {
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
position: relative; position: relative;
padding: 1rem; padding: 1.5rem;
border-radius: 12px; border-radius: 12px;
overflow: hidden; overflow: hidden;
max-height: 500px; max-height: 1000px;
margin-bottom: 1.5rem;
border: 1px solid rgba(255, 255, 255, 0.05);
} }
.step-active { .step-active {
background: rgba(56, 189, 248, 0.05); background: rgba(56, 189, 248, 0.05);
border: 1px solid rgba(56, 189, 248, 0.2); border: 1px solid rgba(56, 189, 248, 0.2);
opacity: 1 !important; opacity: 1 !important;
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.3);
} }
.step-completed { .step-completed {
max-height: 50px; max-height: 80px;
opacity: 0.6; opacity: 0.7;
cursor: pointer; cursor: pointer;
padding-top: 0.5rem; padding-top: 1rem;
padding-bottom: 0.5rem; padding-bottom: 1rem;
background: rgba(255, 255, 255, 0.02);
} }
.step-completed:hover { .step-completed:hover {
opacity: 1; opacity: 1;
background: rgba(255, 255, 255, 0.02); background: rgba(255, 255, 255, 0.04);
border-color: rgba(56, 189, 248, 0.3);
} }
.step-completed select, .step-completed select,
.step-completed input { .step-completed input,
display: none; .step-completed .relative,
.step-completed .pt-8,
.step-completed .grid {
display: none !important;
} }
.step-summary { .step-summary {
font-size: 0.8rem; font-size: 0.85rem;
color: #38bdf8; color: #38bdf8;
font-weight: 700; font-weight: 700;
margin-top: -2px; margin-top: 0.5rem;
background: rgba(56, 189, 248, 0.1); background: rgba(56, 189, 248, 0.1);
padding: 2px 8px; padding: 6px 12px;
border-radius: 4px; border-radius: 8px;
display: inline-block; display: block;
width: fit-content;
border: 1px solid rgba(56, 189, 248, 0.2);
} }
.spinner { .spinner {
@@ -292,6 +302,7 @@
<label class="field-label">3. Secondary Breakdown (Series)</label> <label class="field-label">3. Secondary Breakdown (Series)</label>
<select id="axisSub" class="input-glass" onchange="updateUrlParams(); proceedToStep(4)"> <select id="axisSub" class="input-glass" onchange="updateUrlParams(); proceedToStep(4)">
<option value="">None (Unified)</option> <option value="">None (Unified)</option>
<option value="isin">By Company / ISIN</option>
<option value="exchange">By Exchange</option> <option value="exchange">By Exchange</option>
<option value="continent">By Continent</option> <option value="continent">By Continent</option>
<option value="sector">By Sector</option> <option value="sector">By Sector</option>
@@ -520,14 +531,22 @@
btn.disabled = true; btn.disabled = true;
loader.classList.remove('hidden'); loader.classList.remove('hidden');
errBox.classList.add('hidden'); errBox.classList.add('hidden');
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 (dates.from) url += `&date_from=${dates.from}`; if (dates.from) url += `&date_from=${dates.from}`;
if (dates.to) url += `&date_to=${dates.to}`; if (dates.to) url += `&date_to=${dates.to}`;
if (isins) url += `&isins=${isins}`; if (isins) url += `&isins=${isins}`;
try { try {
// If multiple ISINs are selected but no breakdown is chosen,
// we automatically breakdown by ISIN to give them different colors.
let effectiveSub = sub;
if (!sub && store.pinnedIsins.length > 1 && x !== 'isin') {
effectiveSub = 'isin';
url += `&sub_group_by=isin`;
} else if (sub) {
url += `&sub_group_by=${sub}`;
}
const res = await fetch(url).then(r => { const res = await fetch(url).then(r => {
if (!r.ok) throw new Error(`Server Error: ${r.status}`); if (!r.ok) throw new Error(`Server Error: ${r.status}`);
return r.json(); return r.json();
@@ -539,7 +558,7 @@
if (x === 'day' || x === 'month') labels.sort((a, b) => new Date(a) - new Date(b)); if (x === 'day' || x === 'month') labels.sort((a, b) => new Date(a) - new Date(b));
let datasets = []; let datasets = [];
if (sub) { if (effectiveSub) {
let seriesNames = [...new Set(data.map(r => r[1]))]; let seriesNames = [...new Set(data.map(r => r[1]))];
seriesNames.forEach((name, idx) => { seriesNames.forEach((name, idx) => {
const hue = (idx * 137.5) % 360; const hue = (idx * 137.5) % 360;
@@ -552,7 +571,8 @@
backgroundColor: `hsla(${hue}, 75%, 50%, 0.7)`, backgroundColor: `hsla(${hue}, 75%, 50%, 0.7)`,
borderColor: `hsla(${hue}, 75%, 50%, 1)`, borderColor: `hsla(${hue}, 75%, 50%, 1)`,
borderWidth: 2, borderWidth: 2,
fill: currentChartType === 'line' fill: currentChartType === 'line',
tension: 0.3
}); });
}); });
} else { } else {
@@ -565,7 +585,8 @@
backgroundColor: '#38bdf866', backgroundColor: '#38bdf866',
borderColor: '#38bdf8', borderColor: '#38bdf8',
borderWidth: 2, borderWidth: 2,
fill: currentChartType === 'line' fill: currentChartType === 'line',
tension: 0.3
}); });
} }
@@ -576,10 +597,31 @@
options: { options: {
responsive: true, maintainAspectRatio: false, responsive: true, maintainAspectRatio: false,
animation: { duration: 800, easing: 'easeOutQuart' }, animation: { duration: 800, easing: 'easeOutQuart' },
scales: { y: { stacked: true, grid: { color: 'rgba(255,255,255,0.05)' } }, x: { stacked: true, grid: { display: false } } }, scales: {
y: {
stacked: (currentChartType === 'bar'),
grid: { color: 'rgba(255,255,255,0.05)' },
ticks: { color: '#64748b' }
},
x: {
stacked: (currentChartType === 'bar'),
grid: { display: false },
ticks: { color: '#64748b' }
}
},
plugins: { plugins: {
legend: { position: 'bottom', labels: { color: '#94a3b8', boxWidth: 12, usePointStyle: true } }, legend: {
tooltip: { backgroundColor: '#1e293b', titleColor: '#38bdf8' } display: true,
position: 'bottom',
labels: { color: '#94a3b8', boxWidth: 12, usePointStyle: true, padding: 20 }
},
tooltip: {
backgroundColor: '#1e293b',
titleColor: '#38bdf8',
bodyColor: '#e2e8f0',
borderColor: 'rgba(255,255,255,0.1)',
borderWidth: 1
}
} }
} }
}); });
@@ -626,18 +668,32 @@
function proceedToStep(n) { function proceedToStep(n) {
document.querySelectorAll('.report-step').forEach((s, idx) => { document.querySelectorAll('.report-step').forEach((s, idx) => {
const stepNum = idx + 1; const stepNum = idx + 1;
// Active step is the one currently being configured
s.classList.toggle('step-active', stepNum === n); s.classList.toggle('step-active', stepNum === n);
s.classList.toggle('step-completed', stepNum < n); // Completed steps are those before the current one, but NOT if they are active
if (stepNum > n) s.classList.add('hidden', 'opacity-50'); s.classList.toggle('step-completed', stepNum < n && stepNum !== n);
else s.classList.remove('hidden');
// Show all steps but fade future ones slightly
s.classList.remove('hidden');
if (stepNum > n) {
s.classList.add('opacity-50');
} else {
s.classList.remove('opacity-50');
}
const summary = document.getElementById(`summary-step${stepNum}`); const summary = document.getElementById(`summary-step${stepNum}`);
if (summary) { if (summary) {
if (stepNum < n) { // Only show summary for non-active steps that have a selection
if (stepNum !== n) {
const input = s.querySelector('select, input'); const input = s.querySelector('select, input');
let val = input ? (input.options && input.selectedIndex >= 0 ? input.options[input.selectedIndex].text : input.value) : ''; let val = input ? (input.options && input.selectedIndex >= 0 ? input.options[input.selectedIndex].text : input.value) : '';
summary.innerText = val; if (val && val !== 'Select...') {
summary.classList.remove('hidden'); summary.innerText = val;
summary.classList.remove('hidden');
} else {
summary.classList.add('hidden');
}
// Allow clicking headers of any step to go back/forward
s.onclick = () => proceedToStep(stepNum); s.onclick = () => proceedToStep(stepNum);
} else { } else {
summary.classList.add('hidden'); summary.classList.add('hidden');