Files
joanna-website/app/views/dashboard/index.html.erb
T
2026-07-28 22:05:33 -06:00

220 lines
7.0 KiB
Plaintext

<h1 style="font-family: Arial, sans-serif;">Dynamic Dashboard</h1>
<div id="filter-area" style="margin-bottom: 20px; display: flex; gap: 15px; font-family: Arial, sans-serif;">
<!-- Dropdowns injected here by JS -->
</div>
<div id="results-area" style="display: flex; gap: 20px;">
<!-- Grids injected here by JS -->
</div>
<style>
.dyn-grid { border-collapse: collapse; border: 2px solid #000; font-family: Arial; font-size: 12px; }
.dyn-grid td { border: 1px solid #000; padding: 4px 8px; text-align: center; }
.bg-dark-blue { background-color: #002060; color: white; font-weight: bold; }
.bg-light-blue { background-color: #D9E1F2; font-weight: bold; }
.bg-pale-yellow { background-color: #FFFFCC; }
.bg-pale-green { background-color: #E2EFDA; }
.bg-pale-orange { background-color: #FCE4D6; }
</style>
<script>
document.addEventListener("DOMContentLoaded", () => {
let manifest = {};
let currentSelections = {};
async function init() {
const resp = await fetch(
'/dashboard',
{
headers: {
'Accept': 'application/json'
}
});
manifest = await resp.json();
await renderRootFilters();
}
async function renderRootFilters() {
const rootFilters = manifest.filters.filter(f => f.depends_on === null);
for (const f of rootFilters) {
await createDropdown(f);
}
//rootFilters.forEach(f => await createDropdown(f));
}
async function createDropdown(config) {
const container = document.createElement("div");
container.id = `container_${config.id}`;
const label = document.createElement("label");
label.textContent = config.label + ": ";
label.style.fontWeight = "bold";
const select = document.createElement("select");
select.id = config.id;
select.innerHTML = `<option value="">Select...</option>`;
// Marked callback as async
select.addEventListener("change", async (e) => {
const val = e.target.value;
currentSelections[config.id] = val;
// 1. Clear dependent filters and data
clearDependents(config.id);
// 2. Load options for children (Fixed with for...of)
const children = manifest.filters.filter(f => f.depends_on === config.id);
for (const child of children) {
const options = await fetchOptions(child, val);
populateDropdown(child.id, options);
}
// 3. Update data grid
updateGrid();
});
container.appendChild(label);
container.appendChild(select);
document.getElementById("filter-area").appendChild(container);
// Special handling for parent items:
if (config.depends_on === null) {
var options = await fetchOptions(config);
populateDropdown(select.id, options)
}
}
async function fetchOptions(config, value) {
var params
if (config.filter_column == undefined) {
console.log(config + "Undefined filter col");
params = new URLSearchParams({
table: config.table,
pluck_column: config.pluck_column,
});
} else {
params = new URLSearchParams({
table: config.table,
pluck_column: config.pluck_column,
filter_column: config.filter_column,
value: value
});
}
const resp = await fetch(`/dashboard/options?${params.toString()}`);
return await resp.json();
}
function populateDropdown(id, options) {
const select = document.getElementById(id) || createMissingDropdown(id);
select.innerHTML = `<option value="">Select...</option>`;
options.forEach(opt => {
select.innerHTML += `<option value="${opt}">${opt}</option>`;
});
// If this is the last dropdown, it should be visible now
const container = document.getElementById(`container_${id}`);
if(container) container.style.display = "block";
}
function createMissingDropdown(id) {
const config = manifest.filters.find(f => f.id === id);
const container = document.createElement("div");
container.id = `container_${id}`;
container.style.display = "none"; // Hidden until parent is selected
const label = document.createElement("label");
label.textContent = config.label + ": ";
const select = document.createElement("select");
select.id = id;
// Marked callback as async
select.addEventListener("change", async (e) => {
currentSelections[id] = e.target.value;
clearDependents(id);
const children = manifest.filters.filter(f => f.depends_on === id);
// Fixed with for...of loop
for (const child of children) {
const opts = await fetchOptions(child, e.target.value);
populateDropdown(child.id, opts);
}
updateGrid();
});
container.appendChild(label);
container.appendChild(select);
document.getElementById("filter-area").appendChild(container);
return select;
}
function clearDependents(id) {
// Recursively find all filters that depend on this ID and reset them
manifest.filters.forEach(f => {
if (f.depends_on === id) {
const select = document.getElementById(f.id);
if (select) select.innerHTML = `<option value="">Select...</option>`;
const container = document.getElementById(`container_${f.id}`);
if (container) container.style.display = "none";
delete currentSelections[f.id];
clearDependents(f.id);
}
});
}
async function updateGrid() {
const params = new URLSearchParams(currentSelections);
const resp = await fetch(`/dashboard/data?${params.toString()}`);
const data = await resp.json();
renderLayout(data);
}
function renderLayout(data) {
const area = document.getElementById("results-area");
area.innerHTML = "";
// Group layout by grid name (defaults to 'main')
const grids = {};
manifest.layout.forEach(item => {
const gridId = item.grid || 'main';
if (!grids[gridId]) grids[gridId] = [];
grids[gridId].push(item);
});
Object.keys(grids).forEach(gridId => {
const table = document.createElement("table");
table.className = "dyn-grid";
const layoutItems = grids[gridId];
const maxRow = Math.max(...layoutItems.map(i => i.row));
for (let r = 0; r <= maxRow; r++) {
const tr = document.createElement("tr");
const rowItems = layoutItems.filter(i => i.row === r);
rowItems.forEach(item => {
const td = document.createElement("td");
td.className = item.class;
td.colSpan = item.colspan || 1;
td.rowSpan = item.rowspan || 1;
if (item.text) {
td.textContent = item.text;
} else if (item.value) {
// Resolve nested JSON path (e.g., "main_info.joint")
const parts = item.value.split('.');
const val = parts.reduce((obj, key) => (obj && obj[key] !== undefined) ? obj[key] : '', data);
td.textContent = val;
}
tr.appendChild(td);
});
table.appendChild(tr);
}
area.appendChild(table);
});
}
init();
});
</script>