cybermax / fedflow /state.js
CyberMax-tools's picture
Deploy fedflow to the hub
6660f26 verified
Raw History Blame Contribute Delete
3.56 kB
/* Coffermap state page: sort/search the county table, CSV, live top contractors per county. © 2026 CyberMax. All rights reserved. */
(function () {
'use strict';
var main = document.querySelector('main'), ST = main.dataset.state, FY = +main.dataset.fy;
var tb = document.querySelector('#t tbody'), rows = Array.prototype.slice.call(tb.rows);
var esc = function (s) { return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) { return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]; }); };
document.getElementById('q').addEventListener('input', function (e) {
var q = FF.norm(e.target.value);
rows.forEach(function (r) { r.hidden = q && FF.norm(r.cells[0].textContent).indexOf(q) < 0; });
});
var dir = {};
document.querySelectorAll('#t th[data-k]').forEach(function (th) {
th.style.cursor = 'pointer';
th.onclick = function () {
var k = +th.dataset.k; dir[k] = !dir[k];
rows.sort(function (a, b) {
var x = a.cells[k], y = b.cells[k];
var r = k === 0 ? x.textContent.localeCompare(y.textContent) : (+x.dataset.v) - (+y.dataset.v);
return dir[k] ? (k === 0 ? r : -r) : (k === 0 ? -r : r);
});
rows.forEach(function (r) { tb.appendChild(r); });
};
});
document.getElementById('csv').onclick = function () {
var data = rows.map(function (r) { return { fips: r.dataset.fips, county: r.cells[0].textContent, state: ST, contracts_usd: r.cells[1].dataset.v, grants_usd: r.cells[2].dataset.v, population: r.cells[3].dataset.v }; });
var a = document.createElement('a');
a.href = URL.createObjectURL(new Blob([FF.toCsv(data, ['fips', 'county', 'state', 'contracts_usd', 'grants_usd', 'population'])], { type: 'text/csv' }));
a.download = 'coffermap-' + ST.toLowerCase() + '-fy' + FY + '.csv'; document.body.appendChild(a); a.click(); a.remove();
};
var det = document.getElementById('detail');
tb.addEventListener('click', function (e) {
var b = e.target.closest('button[data-county]'); if (!b) return;
var tr = b.closest('tr'), name = tr.cells[0].textContent;
det.hidden = false;
det.innerHTML = '<h3>Top contractors in ' + esc(name) + ', ' + ST + ' (FY' + FY + ')</h3><p class="muted">Asking USAspending.gov… this can take up to a minute.</p>';
det.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
fetch('https://api.usaspending.gov/api/v2/search/spending_by_category/recipient/', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(FF.countyTopBody(ST, b.dataset.county, FY, 10)) })
.then(function (r) { if (!r.ok) throw new Error('USAspending returned ' + r.status); return r.json(); })
.then(function (d) {
var list = (d.results || []).map(function (x) { return '<li><a href="https://www.usaspending.gov/recipient/' + encodeURIComponent(x.recipient_id) + '/latest" rel="noopener" target="_blank">' + esc(FF.titleCase(x.name)) + '</a> <span class="num">' + FF.money(x.amount) + '</span></li>'; }).join('');
det.innerHTML = '<h3>Top contractors in ' + esc(name) + ', ' + ST + ' (FY' + FY + ')</h3>' + (list ? '<ol class="toplist">' + list + '</ol>' : '<p class="muted">No contract awards reported for this county.</p>') + '<p class="muted small">Live from USAspending.gov, place of performance. Obligations can be negative after de-obligations.</p>';
})
.catch(function (err) { det.innerHTML = '<p class="msg bad">Could not reach USAspending.gov (' + esc(err.message) + '). Try again.</p>'; });
});
})();