<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>Fill · Vendors (Clickable Prototype)</title>
  <style>
    :root {
      --bg: #0b1020;
      --panel: #ffffff;
      --muted: #f5f6f8;
      --text: #1f2937;
      --text-weak: #6b7280;
      --border: #e5e7eb;
      --brand: #111827;
      --green: #10b981;
      --amber: #f59e0b;
      --red: #ef4444;
      --blue: #3b82f6;
      --purple: #8b5cf6;
      --shadow: 0 10px 30px rgba(2, 6, 23, 0.08);
      --radius: 14px;
    }
    * { box-sizing: border-box; }
    html, body { height: 100%; }
    body {
      margin: 0; font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial;
      color: var(--text); background: #f3f4f6;
    }
    .app { display: grid; grid-template-columns: 260px 1fr; min-height: 100vh; }
    aside {
      background: var(--brand); color: white; padding: 24px 18px; position: sticky; top: 0; height: 100vh;
    }
    .logo { font-weight: 800; font-size: 28px; letter-spacing: 0.4px; margin-bottom: 24px; }
    .nav a { display: flex; align-items: center; gap: 10px; color: #cbd5e1; text-decoration: none; padding: 10px 12px; border-radius: 10px; }
    .nav a.active, .nav a:hover { background: rgba(255,255,255,0.08); color: #fff; }

    header.top { display: flex; align-items: center; justify-content: space-between; margin-bottom: 14px; }
    main { padding: 24px; }
    .page { background: var(--panel); border-radius: var(--radius); box-shadow: var(--shadow); padding: 22px; }

    .kpis { display: grid; grid-template-columns: repeat(4, minmax(0,1fr)); gap: 14px; margin-bottom: 14px; }
    .kpi { background: var(--muted); border: 1px solid var(--border); border-radius: 14px; padding: 16px; cursor: pointer; }
    .kpi h3 { margin: 0 0 6px 0; font-size: 13px; font-weight: 600; color: var(--text-weak); }
    .kpi .val { display: flex; align-items: baseline; gap: 10px; font-size: 24px; font-weight: 700; }
    .kpi small { color: var(--text-weak); }

    .toolbar { display:flex; gap: 8px; align-items: center; margin: 10px 0 12px; }
    .search { position: relative; flex: 1; }
    .search input { width: 100%; padding: 12px 14px 12px 38px; border-radius: 12px; border: 1px solid var(--border); }
    .search svg { position:absolute; left: 12px; top: 50%; transform: translateY(-50%); }
    .btn { border:1px solid var(--border); background: white; padding: 10px 12px; border-radius: 12px; cursor:pointer; }
    .btn[aria-pressed="true"] { outline: 2px solid var(--blue); }
    .btn.primary { background: var(--blue); border-color: var(--blue); color: white; }

    .table-wrap { overflow:auto; border:1px solid var(--border); border-radius: 12px; background: white; }
    table { width:100%; border-collapse: collapse; font-size: 14px; }
    thead th { background: #fbfbfc; position: sticky; top: 0; z-index: 1; text-align:left; font-weight: 600; color: #374151; border-bottom:1px solid var(--border); padding: 12px; }
    tbody td { border-top:1px solid var(--border); padding: 12px; white-space: nowrap; }
    tbody tr:hover { background: #fafafa; }

    .chip { display:inline-flex; align-items:center; gap:6px; padding:4px 8px; border-radius:999px; font-size:12px; border:1px solid var(--border); background:#fff; }
    .state.Active { background:#ecfdf5; border-color:#bbf7d0; color:#065f46; }
    .state.Probation { background:#fffbeb; border-color:#fde68a; color:#92400e; }
    .state.Blocked { background:#fef2f2; border-color:#fecaca; color:#991b1b; }

    .risk.ok { background:#ecfdf5; color:#065f46; border-color:#bbf7d0; }
    .risk.warn { background:#fff7ed; color:#9a3412; border-color:#fed7aa; }
    .risk.block { background:#fef2f2; color:#991b1b; border-color:#fecaca; }

    .health { display:inline-flex; align-items:center; gap:6px; font-weight:600; }
    .dot { width:10px; height:10px; border-radius:999px; display:inline-block; }

    .flags { display:flex; gap:6px; }
    .flag { font-size: 11px; background:#eef2ff; color:#3730a3; border:1px solid #c7d2fe; padding:3px 8px; border-radius:999px; }

    .dropdown { position: relative; }
    .menu { position:absolute; right:0; top:110%; background:white; border:1px solid var(--border); border-radius:12px; padding:8px; box-shadow: var(--shadow); display:none; min-width: 220px; }
    .menu.open { display:block; }
    .menu label { display:flex; align-items:center; gap:10px; padding:8px; border-radius:8px; }
    .menu label:hover { background:#f3f4f6; }

    .bulkbar { display:none; align-items:center; justify-content:space-between; background:#111827; color:white; padding:10px 14px; border-radius:12px; margin: 0 0 12px; }
    .bulkbar button { background:white; color:#111827; border:0; padding:8px 12px; border-radius:8px; }

    .side { position: fixed; top:0; right:-520px; width:520px; height:100%; background:white; box-shadow: -10px 0 30px rgba(2,6,23,.2); transition:right .28s ease; display:flex; flex-direction:column; }
    .side.open { right:0; }
    .side header { padding:18px 18px 12px; border-bottom: 1px solid var(--border); }
    .side .content { padding: 16px 18px; overflow:auto; }
    .pill { padding:3px 8px; border-radius:999px; font-size:12px; background:#eef2ff; }

    .split { display:grid; grid-template-columns: repeat(4, minmax(0,1fr)); gap: 8px; }
    .split small { color: var(--text-weak); }

    @media (max-width: 1200px) { .kpis { grid-template-columns: repeat(2, 1fr);} }
    @media (max-width: 900px) { .app { grid-template-columns: 1fr; } aside { position: relative; height:auto; } }
  </style>
</head>
<body>
<div class="app">
  <aside>
    <div class="logo">Fill</div>
    <nav class="nav" aria-label="Main">
      <a href="#" class="active">Vendors</a>
      <a href="#">Projects</a>
      <a href="#">Consultants</a>
      <a href="#">Candidates</a>
      <a href="#">Analytics</a>
      <a href="#">Settings</a>
    </nav>
    <div style="margin-top:24px; font-size:12px; color:#9ca3af">This is a clickable prototype. Data is synthetic.</div>
  </aside>
  <main>
    <div class="page">
      <header class="top">
        <h1 style="margin:0; font-size: 24px;">Vendors</h1>
        <div class="split" id="vendorSplit"></div>
      </header>

      <section class="kpis" id="kpis"></section>

      <div class="toolbar" role="region" aria-label="Filters and actions">
        <div class="search" style="max-width: 500px;">
          <svg width="18" height="18" fill="none" stroke="#6b7280" stroke-width="2" viewBox="0 0 24 24" aria-hidden="true"><circle cx="11" cy="11" r="7"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
          <input id="nlq" placeholder="Ask Fill… e.g. ‘Sweden consultancies with expiring docs’" aria-label="Natural language search" />
        </div>
        <button class="btn" id="structuredBtn" title="Structured filters">Filters</button>
        <div class="dropdown">
          <button class="btn" id="columnsBtn" aria-haspopup="true" aria-expanded="false">Columns</button>
          <div class="menu" id="columnsMenu" role="menu" aria-label="Columns"></div>
        </div>
        <div class="dropdown">
          <button class="btn" id="viewsBtn">Views</button>
          <div class="menu" id="viewsMenu">
            <button class="btn" id="saveView" style="width:100%;">Save current view</button>
          </div>
        </div>
        <button class="btn primary" id="newVendor">New vendor</button>
      </div>

      <div class="bulkbar" id="bulkbar">
        <div><strong id="selCount">0</strong> selected</div>
        <div style="display:flex; gap:8px;">
          <button id="bulkState">Change state</button>
          <button id="bulkDocs">Request docs</button>
          <button id="bulkExport">Export CSV</button>
          <button id="bulkClear" title="Clear selection">Clear</button>
        </div>
      </div>

      <div class="table-wrap">
        <table id="grid" aria-label="Vendors table">
          <thead><tr id="thead"></tr></thead>
          <tbody id="tbody"></tbody>
        </table>
      </div>
    </div>
  </main>
</div>

<!-- Side sheet (vendor profile) -->
<aside class="side" id="side">
  <header>
    <div style="display:flex; align-items:center; justify-content: space-between; gap:12px;">
      <div>
        <div id="sideName" style="font-size:18px; font-weight:700;">Vendor</div>
        <div style="display:flex; gap:8px; margin-top:6px; align-items:center;">
          <span id="sideState" class="chip state Active">Active</span>
          <span id="sideHealth" class="chip">Health: 84</span>
          <span id="sideSpend" class="chip">Spend YTD: 0</span>
          <span id="sideTasks" class="chip">Open actions: 0</span>
        </div>
      </div>
      <button class="btn" onclick="closeSide()">Close</button>
    </div>
  </header>
  <div class="content" id="sideContent">
    <!-- filled dynamically -->
  </div>
</aside>

<script>
(function(){
  // --- Data ---------------------------------------------------------------
  const vendors = [
    {id:1, name:'Knowit', state:'Active', health:86, assignments:{active:23,total:68}, spend:23000, contracted:23000, invoiced:23000, risk:'ok', geo:'Sweden', type:'Broker', flags:['Preferred'], created:'2023-04-13', expiryDays:180},
    {id:2, name:'Sigma', state:'Active', health:77, assignments:{active:15,total:17}, spend:3200, contracted:2000, invoiced:3200, risk:'ok', geo:'Sweden', type:'Consultancy', flags:['Preferred'], created:'2023-04-15', expiryDays:45},
    {id:3, name:'Tingent', state:'Active', health:81, assignments:{active:1,total:1}, spend:15000, contracted:15000, invoiced:15000, risk:'ok', geo:'Sweden', type:'Consultancy', flags:[''], created:'2023-04-12', expiryDays:120},
    {id:4, name:'HiQ', state:'Active', health:72, assignments:{active:15,total:16}, spend:6000, contracted:6000, invoiced:6000, risk:'ok', geo:'Sweden', type:'Broker', flags:[''], created:'2023-04-16', expiryDays:14},
    {id:5, name:'The Stellar collective', state:'Active', health:74, assignments:{active:3,total:9}, spend:15000, contracted:15000, invoiced:15000, risk:'ok', geo:'Sweden, Norway +3', type:'Consultancy', flags:[''], created:'2023-04-16', expiryDays:75},
    {id:6, name:'Curamando', state:'Active', health:69, assignments:{active:23,total:39}, spend:24000, contracted:24000, invoiced:24000, risk:'ok', geo:'Sweden', type:'Broker', flags:['Marketplace'], created:'2023-04-17', expiryDays:200},
    {id:7, name:'Wise professionals', state:'Active', health:65, assignments:{active:3,total:6}, spend:500, contracted:1000, invoiced:500, risk:'ok', geo:'Sweden', type:'Staffing agency', flags:[''], created:'2023-04-14', expiryDays:365},
    {id:8, name:'Ework', state:'Probation', health:58, assignments:{active:0,total:0}, spend:0, contracted:0, invoiced:0, risk:'warn', geo:'Sweden, Poland', type:'Consultancy', flags:['Marketplace'], created:'2023-04-18', expiryDays:25},
    {id:9, name:'Newr', state:'Blocked', health:40, assignments:{active:15,total:69}, spend:6000, contracted:6000, invoiced:6000, risk:'block', geo:'Sweden', type:'Freelancer', flags:[''], created:'2023-04-11', expiryDays:5},
  ];

  // Column configuration (toggle-able)
  const columns = [
    {key:'sel', label:'', width:28, render: r => `<input type="checkbox" class="rowcheck" data-id="${r.id}">`},
    {key:'name', label:'Vendor', sortable:true, render:r=>`<button class="btn" style="padding:6px 10px" data-open="${r.id}">${escapeHtml(r.name)}</button>`},
    {key:'state', label:'State', sortable:true, render:r=>`<span class="chip state ${r.state}">${r.state}</span>`},
    {key:'health', label:'Health', sortable:true, render:r=>{
      const color = r.health>=75? '#10b981' : r.health>=60? '#f59e0b' : '#ef4444';
      return `<span class="health"><span class="dot" style="background:${color}"></span>${r.health}</span>`;
    }},
    {key:'assign', label:'Assignments', sortable:true, render:r=>`${r.assignments.active} | ${r.assignments.total}`},
    {key:'spend', label:'Spend (k SEK)', sortable:true, render:r=> formatK(r.spend)},
    {key:'variance', label:'Variance %', sortable:true, render:r=> variancePill(r)},
    {key:'risk', label:'Risk', sortable:true, render:r=> riskPill(r)},
    {key:'geo', label:'Geography', sortable:true, render:r=> escapeHtml(r.geo)},
    {key:'type', label:'Type', sortable:true, render:r=> escapeHtml(r.type)},
    {key:'flags', label:'Flags', sortable:false, render:r=> flagsCell(r.flags)}
  ];

  const defaultVisible = ['sel','name','state','health','assign','spend','variance','risk','geo','type','flags'];

  // --- State --------------------------------------------------------------
  let state = {
    sortKey: 'name', sortDir: 'asc',
    visible: JSON.parse(localStorage.getItem('fill_vendor_cols')||'null') || defaultVisible,
    filter: null, // function(row)=>bool
    selection: new Set(),
  };

  // --- Elements -----------------------------------------------------------
  const thead = document.getElementById('thead');
  const tbody = document.getElementById('tbody');
  const menu = document.getElementById('columnsMenu');
  const columnsBtn = document.getElementById('columnsBtn');
  const bulkbar = document.getElementById('bulkbar');
  const selCount = document.getElementById('selCount');
  const nlq = document.getElementById('nlq');

  // --- Renderers ----------------------------------------------------------
  function renderHeader(){
    thead.innerHTML = '';
    visibleCols().forEach(col => {
      const th = document.createElement('th'); th.textContent = col.label; th.style.minWidth = (col.width||120)+ 'px';
      if (col.key && col.sortable){
        th.style.cursor = 'pointer'; th.title = 'Sort';
        th.addEventListener('click', () => sortBy(col.key));
      }
      thead.appendChild(th);
    });
  }

  function renderRows(){
    const data = vendors.filter(r => state.filter? state.filter(r) : true)
      .sort((a,b)=> compare(a,b,state.sortKey,state.sortDir));
    tbody.innerHTML = data.map(r => `<tr data-id="${r.id}">` + visibleCols().map(col => `<td>${col.render(r)}</td>`).join('') + `</tr>`).join('');

    // Wire row checkbox events
    document.querySelectorAll('.rowcheck').forEach(cb=>{
      cb.addEventListener('change', () => { cb.checked? state.selection.add(+cb.dataset.id) : state.selection.delete(+cb.dataset.id); updateBulkbar(); });
    });
    // Wire open profile buttons
    document.querySelectorAll('[data-open]').forEach(btn=> btn.addEventListener('click', ()=> openSide(+btn.dataset.open)));
    // Wire risk pill click -> open compliance section
    document.querySelectorAll('[data-compliance]').forEach(el=> el.addEventListener('click', ()=> openSide(+el.dataset.compliance, 'risk')));
  }

  function renderColumnsMenu(){
    menu.innerHTML = '';
    columns.forEach(col => {
      if(!col.key) return;
      const id = 'col_' + col.key;
      const row = document.createElement('label');
      row.innerHTML = `<input type="checkbox" id="${id}"> <span>${col.label||col.key}</span>`;
      const input = row.querySelector('input');
      input.checked = state.visible.includes(col.key);
      input.addEventListener('change', () => {
        if (input.checked) state.visible.push(col.key); else state.visible = state.visible.filter(k=>k!==col.key);
        persistCols(); renderHeader(); renderRows(); updateColumnsButton();
      });
      menu.appendChild(row);
    });
  }

  function renderKPIs(){
    const wrap = document.getElementById('kpis');
    const goodStanding = vendors.filter(v=> v.health>=75 && v.risk==='ok').length;
    const atRisk = vendors.filter(v=> v.risk!=='ok' || v.state!=='Active').length;
    const spendYTD = vendors.reduce((s,v)=> s+v.invoiced, 0);
    const expiring = vendors.filter(v=> v.expiryDays <= 60).length;
    wrap.innerHTML = '' +
      kpiCard('Vendors in good standing', goodStanding, '', ()=> setFilter(r=> r.health>=75 && r.risk==='ok')) +
      kpiCard('Vendors at risk', atRisk, '', ()=> setFilter(r=> r.risk!=='ok' || r.state!=='Active')) +
      kpiCard('Spend YTD', formatK(spendYTD) + ' SEK', 'invoices', ()=> openSpendView()) +
      kpiCard('Contracts expiring < 60d', expiring, '', ()=> setFilter(r=> r.expiryDays<=60));

    renderVendorSplit();
  }

  function renderVendorSplit(){
    const out = document.getElementById('vendorSplit');
    const byType = {};
    vendors.forEach(v=> { byType[v.type] = (byType[v.type]||0)+1; })
    out.innerHTML = Object.entries(byType).map(([t,c])=>`<div class="kpi" style="padding:8px;" role="button" tabindex="0" onclick="(${setFilter})(r=>r.type==='${t}')"><div class="val" style="font-size:18px;">${c}</div><small>${t}</small></div>`).join('');
  }

  function kpiCard(title, value, sub, onClick){
    const id = 'kpi_' + Math.random().toString(36).slice(2);
    setTimeout(()=> {
      const el = document.getElementById(id); if (el) el.addEventListener('click', onClick);
    }, 0);
    return `<div class="kpi" id="${id}" role="button" tabindex="0"><h3>${title}</h3><div class="val">${value}</div>${sub?`<small>${sub}</small>`:''}</div>`;
  }

  // --- Helpers ------------------------------------------------------------
  function visibleCols(){ return columns.filter(c=> !c.key || state.visible.includes(c.key)); }
  function persistCols(){ localStorage.setItem('fill_vendor_cols', JSON.stringify(state.visible)); }
  function sortBy(key){ state.sortKey=key; state.sortDir = state.sortDir==='asc'?'desc':'asc'; renderHeader(); renderRows(); }
  function compare(a,b,key,dir){
    let va; switch(key){
      case 'assign': va = a.assignments.active; break;
      case 'spend': va = a.spend; break;
      case 'variance': va = (a.invoiced - a.contracted) / (a.contracted||1); break;
      default: va = a[key];
    }
    let vb; switch(key){
      case 'assign': vb = b.assignments.active; break;
      case 'spend': vb = b.spend; break;
      case 'variance': vb = (b.invoiced - b.contracted) / (b.contracted||1); break;
      default: vb = b[key];
    }
    const s = (va>vb?1:va<vb?-1:0); return dir==='asc'? s : -s;
  }
  function riskPill(r){
    const map = {ok:'ok', warn:'warn', block:'block'}; const label = r.risk==='ok'?'OK': r.risk==='warn'? 'Expiring' : 'Missing';
    return `<button class="chip risk ${map[r.risk]}" data-compliance="${r.id}" title="Open compliance">${label}</button>`;
  }
  function flagsCell(flags){
    return `<div class="flags">${flags.filter(Boolean).map(f=>`<span class="flag">${escapeHtml(f)}</span>`).join('')}</div>`;
  }
  function variancePill(r){
    const diff = (r.invoiced - r.contracted) / (r.contracted||1);
    const pct = Math.round(diff*100);
    const color = diff>0? 'var(--red)' : diff<0? 'var(--green)' : '#6b7280';
    return `<span class="pill" style="background:#f3f4f6; color:${color};">${pct>0?'+':''}${pct}%</span>`;
  }
  function formatK(n){ return Math.round(n/1)/1 >= 1000 ? (Math.round(n/1000)) + ' k' : Math.round(n) + '' }
  function escapeHtml(s){ return (s+"").replace(/[&<>]/g, c=> ({'&':'&amp;','<':'&lt;','>':'&gt;'}[c])); }

  function updateBulkbar(){
    const count = state.selection.size; selCount.textContent = count;
    bulkbar.style.display = count? 'flex':'none';
  }
  function setFilter(fn){ state.filter = fn; state.selection.clear(); updateBulkbar(); renderRows(); }

  // --- Interactions -------------------------------------------------------
  columnsBtn.addEventListener('click', () => toggleMenu('columnsMenu'));
  document.getElementById('viewsBtn').addEventListener('click', () => toggleMenu('viewsMenu'));
  function toggleMenu(id){
    document.querySelectorAll('.menu').forEach(m=> m.classList.remove('open'));
    const el = document.getElementById(id); el.classList.toggle('open');
    document.getElementById('columnsBtn').setAttribute('aria-expanded', String(document.getElementById('columnsMenu').classList.contains('open')));
  }
  document.addEventListener('click', (e)=>{
    if(!e.target.closest('.dropdown')){ document.querySelectorAll('.menu').forEach(m=> m.classList.remove('open')); }
  });

  document.getElementById('bulkClear').addEventListener('click', ()=>{ state.selection.clear(); document.querySelectorAll('.rowcheck').forEach(c=>c.checked=false); updateBulkbar(); });
  document.getElementById('bulkDocs').addEventListener('click', ()=> alert('Requested missing/expiring documents from selected vendors.'));
  document.getElementById('bulkState').addEventListener('click', ()=> alert('State change wizard would open (Active/Probation/Blocked).'));
  document.getElementById('bulkExport').addEventListener('click', ()=> exportCSV());

  nlq.addEventListener('keydown', (e)=>{ if(e.key==='Enter') runNLQ(nlq.value); });

  function runNLQ(q){
    q = (q||'').toLowerCase();
    if(!q){ state.filter=null; renderRows(); return; }
    const geo = ['sweden','norway','poland'];
    const types = ['broker','consultancy','staffing agency','freelancer'];
    setFilter(r=>{
      const s = q;
      if(s.includes('risk')){ if(r.risk==='ok') return false; }
      if(s.includes('expir')){ if(r.expiryDays>60) return false; }
      if(s.includes('active')){ if(r.state.toLowerCase()!=='active') return false; }
      if(s.includes('probation')){ if(r.state.toLowerCase()!=='probation') return false; }
      if(s.includes('blocked')){ if(r.state.toLowerCase()!=='blocked') return false; }
      for(const g of geo){ if(s.includes(g) && !r.geo.toLowerCase().includes(g)) return false; }
      for(const t of types){ if(s.includes(t) && r.type.toLowerCase()!==t) return false; }
      if(s.includes('preferred')){ if(!r.flags.map(x=>x.toLowerCase()).includes('preferred')) return false; }
      return [r.name,r.type,r.geo,(r.flags||[]).join(' ')].join(' ').toLowerCase().includes(q.replace(/\s+/g,' ').trim().split(' ')[0]||'');
    });
  }

  function openSpendView(){ alert('This would open the spend dashboard with trend and top vendors.'); }

  // Side sheet
  function openSide(id, section){
    const v = vendors.find(x=>x.id===id); if(!v) return;
    document.getElementById('sideName').textContent = v.name;
    const st = document.getElementById('sideState'); st.textContent = v.state; st.className = 'chip state ' + v.state;
    document.getElementById('sideHealth').textContent = 'Health: ' + v.health;
    document.getElementById('sideSpend').textContent = 'Spend YTD: ' + v.invoiced + ' SEK';
    document.getElementById('sideTasks').textContent = 'Open actions: ' + (v.risk!=='ok'?1:0);

    const html = `
      <h3>Overview</h3>
      <p><strong>Type:</strong> ${escapeHtml(v.type)} · <strong>Geography:</strong> ${escapeHtml(v.geo)}</p>
      <div style="display:flex; gap:8px; margin:8px 0;">${flagsCell(v.flags)}</div>
      <h3>Performance</h3>
      <ul>
        <li>Active assignments: <strong>${v.assignments.active}</strong> · Total: ${v.assignments.total}</li>
        <li>Health drivers: Compliance 40 · Risk 30 · Invoice accuracy 20 · Dispute rate 10</li>
      </ul>
      <h3 id="risk">Compliance & Risk</h3>
      <p>Risk status: ${riskPill(v)} · Contract expiry in <strong>${v.expiryDays}</strong> days.</p>
      <h3>Financials</h3>
      <ul>
        <li>Contracted: ${v.contracted} SEK</li>
        <li>Invoiced: ${v.invoiced} SEK</li>
        <li>Variance: ${((v.invoiced - v.contracted)/(v.contracted||1) * 100).toFixed(1)}%</li>
      </ul>
      <h3>Docs & Notes</h3>
      <p>Drag & drop a file here in the real app to auto-extract clauses. (Prototype stub)</p>
    `;
    document.getElementById('sideContent').innerHTML = html;

    const side = document.getElementById('side'); side.classList.add('open');
    if(section==='risk'){
      setTimeout(()=>{
        const el = document.querySelector('#risk'); if(el) el.scrollIntoView({behavior:'smooth'});
      }, 80);
    }
  }
  window.openSide = openSide; window.closeSide = function(){ document.getElementById('side').classList.remove('open'); };

  // Export CSV
  function exportCSV(){
    const rows = vendors.filter(r=> state.selection.has(r.id));
    if(rows.length===0){ alert('Select rows first.'); return; }
    const fields = ['name','state','health','assign_active','assign_total','spend','contracted','invoiced','risk','geo','type','flags'];
    const lines = [fields.join(',')].concat(rows.map(r=> [
      r.name, r.state, r.health, r.assignments.active, r.assignments.total, r.spend, r.contracted, r.invoiced, r.risk, r.geo, r.type, (r.flags||[]).join('|')
    ].map(v=>`"${String(v).replace(/"/g,'\"')}"`).join(',')));
    const blob = new Blob([lines.join('\n')], {type:'text/csv'});
    const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = 'vendors_export.csv'; a.click();
  }

  // Column menu button state
  function updateColumnsButton(){
    columnsBtn.textContent = 'Columns (' + state.visible.filter(k=>k!=='sel').length + ')';
  }

  // Init
  renderHeader(); renderRows(); renderColumnsMenu(); renderKPIs(); updateColumnsButton();
})();
</script>
</body>
</html>
What you get with your free account:
Post your first job
Find expert consultants
Meet highly niched and specialized recruiters
No credit card required
Making Talent acquisition professionals happier
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.
Headhunter or independent consultant? Request access here