File size: 3,560 Bytes
19a0974
16c3ef4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6660f26
16c3ef4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/* 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>'; });
  });
})();