const DashboardApp = {
init() {
document.querySelectorAll('.layout-tabs').forEach(layout => this.setupTabs(layout));
document.querySelectorAll('.section-table-sortable').forEach(t => this.setupSort(t));
this.setupRefresh();
this.setupDrillDown();
},
setupRefresh() {
const btn = document.getElementById('dashboard-refresh');
if (!btn) return;
const status = document.getElementById('refresh-status');
const title = (document.querySelector('.mcp-app-title')?.textContent ?? 'this dashboard').trim();
const refresh = async () => {
if (btn.disabled) return;
btn.disabled = true;
btn.setAttribute('aria-busy', 'true');
if (status) status.textContent = 'Asking for fresh data…';
try {
await McpAppBridge.sendMessage(`Refresh ${title} with current data.`);
if (status) status.textContent = 'Requested.';
} catch (err) {
console.error('Dashboard refresh failed:', err);
if (status) status.textContent = 'Could not request a refresh.';
} finally {
btn.disabled = false;
btn.removeAttribute('aria-busy');
}
};
btn.addEventListener('click', refresh);
const secs = Number(btn.dataset.refreshInterval);
if (Number.isFinite(secs) && secs > 0) {
setInterval(() => {
if (document.visibilityState === 'visible') refresh();
}, Math.max(30, secs) * 1000);
}
},
setupDrillDown() {
const grid = document.querySelector('.dashboard[data-drill-down="true"]');
if (!grid) return;
for (const row of grid.querySelectorAll('.section-table tbody tr')) {
row.tabIndex = 0;
row.classList.add('is-drillable');
const ask = () => {
const cells = Array.from(row.children).map(c => c.textContent.trim());
McpAppBridge.sendMessage(`Tell me more about this row: ${cells.join(' | ')}`)
.catch(err => console.error('Drill-down failed:', err));
};
row.addEventListener('click', ask);
row.addEventListener('keydown', (e) => {
if (e.key === 'Enter') { e.preventDefault(); ask(); }
});
}
},
setupSort(table) {
const tbody = table.querySelector('tbody');
if (!tbody) return;
const headers = Array.from(table.querySelectorAll('th.sortable'));
headers.forEach((th, index) => {
let direction = 'asc';
const sort = () => {
const rows = Array.from(tbody.querySelectorAll('tr'));
rows.sort((a, b) => {
const av = (a.children[index]?.textContent ?? '').trim();
const bv = (b.children[index]?.textContent ?? '').trim();
const an = Number(av.replace(/,/g, ''));
const bn = Number(bv.replace(/,/g, ''));
const cmp = (av !== '' && bv !== '' && Number.isFinite(an) && Number.isFinite(bn))
? an - bn
: av.localeCompare(bv, undefined, { numeric: true });
return direction === 'asc' ? cmp : -cmp;
});
rows.forEach(r => tbody.appendChild(r));
headers.forEach(h => {
h.classList.remove('sort-asc', 'sort-desc');
h.removeAttribute('aria-sort');
});
th.classList.add(direction === 'asc' ? 'sort-asc' : 'sort-desc');
th.setAttribute('aria-sort', direction === 'asc' ? 'ascending' : 'descending');
direction = direction === 'asc' ? 'desc' : 'asc';
};
th.addEventListener('click', sort);
th.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); sort(); }
});
});
},
setupTabs(layout) {
const buttons = Array.from(layout.querySelectorAll('.tab-btn'));
const sections = Array.from(layout.querySelectorAll('.dashboard-section'));
sections.forEach((section, i) => {
section.setAttribute('role', 'tabpanel');
section.setAttribute('aria-labelledby', `tab-${section.id}`);
if (section.getAttribute('tabindex') === null) section.tabIndex = 0;
section.hidden = i > 0;
});
buttons.forEach((btn, i) => {
btn.addEventListener('click', () => this.select(i, buttons, sections));
btn.addEventListener('keydown', (e) => {
const last = buttons.length - 1;
let next = null;
if (e.key === 'ArrowRight' || e.key === 'ArrowDown') next = i === last ? 0 : i + 1;
else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') next = i === 0 ? last : i - 1;
else if (e.key === 'Home') next = 0;
else if (e.key === 'End') next = last;
if (next !== null) {
e.preventDefault();
this.select(next, buttons, sections);
buttons[next].focus();
}
});
});
layout.classList.add('tabs-ready');
},
select(index, buttons, sections) {
buttons.forEach((btn, i) => {
const selected = i === index;
btn.classList.toggle('active', selected);
btn.setAttribute('aria-selected', String(selected));
btn.tabIndex = selected ? 0 : -1;
});
const target = buttons[index].dataset.target;
sections.forEach(section => {
section.hidden = section.id !== target;
});
}
};
document.addEventListener('DOMContentLoaded', () => DashboardApp.init());