Files

131 lines
4.0 KiB
JavaScript
Raw Permalink Normal View History

// No framework, no build step — this is a plain JSON file (site/data.json,
// generated by scripts/build.py) rendered client-side. Keeps the "add a
// device" contribution path as simple as adding one YAML file.
let reports = [];
let sortKey = "device_model";
let sortDir = 1;
const rowsEl = document.getElementById("rows");
const searchEl = document.getElementById("search");
const statusEl = document.getElementById("status-filter");
const formFactorEl = document.getElementById("form-factor-filter");
const countEl = document.getElementById("count");
const emptyEl = document.getElementById("empty");
function storageLabel(storage) {
return `${storage.size_gb}GB ${storage.type.toUpperCase()}`;
}
function matchesFilters(r, query, status, formFactor) {
if (status && r.status !== status) return false;
if (formFactor && r.form_factor !== formFactor) return false;
if (!query) return true;
const haystack = [r.device_model, r.cpu, r.wifi_chip, r.ethernet]
.filter(Boolean)
.join(" ")
.toLowerCase();
return haystack.includes(query);
}
function sortValue(r, key) {
if (key === "storage") return r.storage.size_gb;
return r[key];
}
function render() {
const query = searchEl.value.trim().toLowerCase();
const status = statusEl.value;
const formFactor = formFactorEl.value;
const filtered = reports.filter((r) => matchesFilters(r, query, status, formFactor));
filtered.sort((a, b) => {
const av = sortValue(a, sortKey);
const bv = sortValue(b, sortKey);
if (av < bv) return -1 * sortDir;
if (av > bv) return 1 * sortDir;
return 0;
});
rowsEl.innerHTML = "";
for (const r of filtered) {
const tr = document.createElement("tr");
tr.className = "expandable";
tr.innerHTML = `
<td>${escapeHtml(r.device_model)}</td>
<td>${escapeHtml(r.form_factor)}</td>
<td>${escapeHtml(r.cpu)}</td>
<td>${r.ram_gb}GB</td>
<td>${storageLabel(r.storage)}</td>
<td>${escapeHtml(r.wifi_chip)}</td>
<td><span class="badge ${r.status}">${r.status}</span></td>
<td>${escapeHtml(r.archy_version)}</td>
<td>${escapeHtml(r.tested_date)}</td>
`;
tr.addEventListener("click", () => toggleDetail(tr, r));
rowsEl.appendChild(tr);
}
countEl.textContent = `${filtered.length} of ${reports.length} report${reports.length === 1 ? "" : "s"}`;
emptyEl.hidden = filtered.length !== 0;
}
function toggleDetail(tr, r) {
const next = tr.nextElementSibling;
if (next && next.classList.contains("detail-row")) {
next.remove();
return;
}
document.querySelectorAll(".detail-row").forEach((el) => el.remove());
const detail = document.createElement("tr");
detail.className = "detail-row";
const parts = [];
if (r.ethernet) parts.push(`<strong>Ethernet:</strong> ${escapeHtml(r.ethernet)}`);
parts.push(`<strong>Install method:</strong> ${escapeHtml(r.install_method)}`);
if (r.submitted_by) parts.push(`<strong>Submitted by:</strong> ${escapeHtml(r.submitted_by)}`);
if (r.issues) parts.push(`<strong>Issues:</strong>\n${escapeHtml(r.issues.trim())}`);
if (r.notes) parts.push(`<strong>Notes:</strong>\n${escapeHtml(r.notes.trim())}`);
const td = document.createElement("td");
td.colSpan = 9;
td.innerHTML = parts.join("\n\n");
detail.appendChild(td);
tr.after(detail);
}
function escapeHtml(s) {
const div = document.createElement("div");
div.textContent = s ?? "";
return div.innerHTML;
}
document.querySelectorAll("th[data-sort]").forEach((th) => {
th.addEventListener("click", () => {
const key = th.dataset.sort;
if (sortKey === key) {
sortDir *= -1;
} else {
sortKey = key;
sortDir = 1;
}
render();
});
});
searchEl.addEventListener("input", render);
statusEl.addEventListener("change", render);
formFactorEl.addEventListener("change", render);
fetch("data.json")
.then((res) => res.json())
.then((data) => {
reports = data;
render();
})
.catch((err) => {
emptyEl.hidden = false;
emptyEl.textContent = "Failed to load data.json — " + err;
});