← back to AbramsOS

views/settlements.ejs

206 lines

<%- include('partials/header', { title: 'Settlements' }) %>
<%
  var TIER = { high:{dot:'🔴',label:'HIGH',color:'#c0392b'}, med:{dot:'🟡',label:'MED',color:'#b8860b'}, low:{dot:'⚪',label:'LOW',color:'#8894a5'} };
  function money(c){ return c==null ? '—' : '$'+(c/100).toLocaleString('en-US'); }
  function proofLabel(s){ return s.no_claim_required ? 'AUTO' : (s.proof_required===false ? 'no proof' : (s.proof_required ? 'proof' : '?')); }
  function deadlinePill(s){ if (s.days==null) return 'no deadline'; if (s.days<0) return 'expired '+(-s.days)+'d ago'; if (s.days===0) return 'DUE TODAY'; if (s.days===1) return 'due tomorrow'; return 'in '+s.days+'d'; }
  function bar(label, v){ var pct=Math.round((v/5)*100); var col=v>=4?'#3fb950':v>=2?'#d29922':'#8894a5'; return '<div class="rbar" title="'+label+' '+v+'/5"><span class="rbar-l">'+label+'</span><span class="rbar-track"><span class="rbar-fill" style="width:'+pct+'%;background:'+col+'"></span></span><span class="rbar-v">'+v+'</span></div>'; }
%>
<style>
  .views{display:flex;gap:6px;margin:8px 0 4px}
  .views button{background:transparent;color:var(--text-dim);border:1px solid rgba(255,255,255,.15);border-radius:7px;padding:5px 12px;cursor:pointer;font-size:13px}
  .views button.on{background:#2d5bff;color:#fff;border-color:#2d5bff}
  .claim-card{border:1px solid rgba(255,255,255,.08);border-radius:10px;padding:14px 16px;margin:10px 0;display:grid;grid-template-columns:44px 1fr 300px;gap:14px;align-items:start}
  .claim-card.expired{opacity:.5}
  .rank-badge{display:flex;flex-direction:column;align-items:center;justify-content:center;width:44px;height:44px;border-radius:10px;background:rgba(255,255,255,.05);font-weight:700}
  .rank-badge .n{font-size:18px;line-height:1}.rank-badge .p{font-size:9px;color:var(--text-dim);margin-top:2px}
  .tier-chip{display:inline-flex;align-items:center;gap:4px;font-size:10px;font-weight:700;padding:2px 7px;border-radius:20px;border:1px solid currentColor}
  .pill{display:inline-block;font-size:11px;padding:2px 8px;border-radius:20px;background:rgba(255,255,255,.06);margin-right:6px}
  .pill.due{color:#f0b429}.pill.today{color:#ff6b6b;font-weight:700}.pill.past{color:#8894a5}
  .rbar{display:flex;align-items:center;gap:6px;margin:3px 0;font-size:11px}
  .rbar-l{width:82px;color:var(--text-dim)}.rbar-track{flex:1;height:6px;border-radius:4px;background:rgba(255,255,255,.08);overflow:hidden}.rbar-fill{display:block;height:100%}.rbar-v{width:14px;text-align:right;color:var(--text-dim)}
  .claim-name{font-weight:600;font-size:14px}.claim-meta{color:var(--text-dim);font-size:11px;margin-top:2px}
  .ctrls{display:flex;flex-direction:column;gap:8px}
  .btn-go{background:#1f9d55;color:#fff;border:none;border-radius:8px;padding:8px 12px;font-weight:700;cursor:pointer;font-size:13px}
  .btn-go:hover{background:#249a58}.btn-auto{opacity:.6;font-weight:400}
  /* list + table */
  .ltbl,.gtbl{width:100%;border-collapse:collapse;font-size:13px}
  .ltbl td,.gtbl td,.gtbl th{padding:7px 9px;border-bottom:1px solid rgba(255,255,255,.07);vertical-align:middle}
  .gtbl th{position:sticky;top:0;background:var(--panel,#161c2b);cursor:pointer;user-select:none;text-align:left;white-space:nowrap;color:var(--text-dim)}
  .gtbl th:hover{color:#fff}
  /* prep modal */
  .backdrop{position:fixed;inset:0;background:rgba(0,0,0,.55);display:none;align-items:center;justify-content:center;z-index:50}
  .prep{background:#161c2b;border:1px solid rgba(255,255,255,.12);border-radius:14px;max-width:560px;width:92vw;max-height:88vh;overflow:auto;padding:18px 20px}
  .prep h3{margin:0 0 2px}.prep .lane{margin:14px 0}.prep .lane h4{margin:0 0 6px;font-size:12px;text-transform:uppercase;letter-spacing:.04em}
  .kv{display:flex;justify-content:space-between;align-items:center;gap:10px;padding:5px 8px;border-radius:7px;background:rgba(255,255,255,.04);margin:4px 0;font-size:13px}
  .kv .k{color:var(--text-dim)}.kv .v{font-weight:600}
  .kv button{background:#2d5bff;color:#fff;border:none;border-radius:6px;padding:3px 9px;cursor:pointer;font-size:11px}
  .lane.green h4{color:#3fb950}.lane.amber h4{color:#d29922}.lane.red h4{color:#ff6b6b}
</style>

<section class="page-head">
  <h1>Settlement Claims</h1>
  <p class="subtle" style="margin:0;color:var(--text-dim);font-size:13px">
    Ranked by <strong>priority</strong>. <strong>Claim it &amp; Go</strong> marks it eligible, prefills your
    identity + payment (PayPal), opens the official claim portal, and <strong>stops at the perjury attestation
    for you to submit</strong>. Nothing is filed automatically.
    <% if (source !== 'db') { %><span style="color:#e0a030">· backfill view (DB offline)</span><% } %>
  </p>
  <div class="views">
    <button data-v="grid" class="on">🃏 Grid</button>
    <button data-v="list">📋 List</button>
    <button data-v="table">▦ Table</button>
  </div>
</section>

<section id="v-grid">
  <% if (!open.length) { %><p class="subtle" style="color:var(--text-dim)">No open claims.</p><% } %>
  <% open.forEach(function(s){ var t=TIER[s.tier]||TIER.low; %>
    <div class="claim-card" data-id="<%= s.id %>">
      <div class="rank-badge"><span class="n">#<%= s.rank %></span><span class="p"><%= s.priority %></span></div>
      <div>
        <div class="claim-name"><% if (s.mode_url){ %><a href="<%= s.mode_url %>" target="_blank" rel="noopener noreferrer"><%= s.name %> ↗</a><% } else { %><%= s.name %><% } %></div>
        <div class="claim-meta"><%= s.category||'' %></div>
        <div style="margin:8px 0">
          <span class="tier-chip" style="color:<%= t.color %>"><%= t.dot %> <%= t.label %></span>
          <span class="pill"><strong><%= money(s.payout_max_cents) %></strong></span>
          <span class="pill <%= s.days===0?'today':(s.days!=null&&s.days<0?'past':'due') %>"><%= deadlinePill(s) %></span>
          <span class="pill"><%= proofLabel(s) %></span>
        </div>
        <% if (s.eligibility_question){ %><div class="claim-meta" style="max-width:520px"><%= s.eligibility_question %></div><% } %>
      </div>
      <div class="ctrls">
        <div class="ratings"><%- bar('value',s.ratings.value) %><%- bar('urgency',s.ratings.urgency) %><%- bar('ease',s.ratings.ease) %><%- bar('win likelihood',s.ratings.winlikelihood) %></div>
        <% if (s.no_claim_required){ %><span class="pill">no form — automatic</span>
        <% } else { %><button class="btn-go go" data-id="<%= s.id %>" data-name="<%= s.name %>">✅ Claim it &amp; Go</button><% } %>
      </div>
    </div>
  <% }) %>
</section>

<section id="v-list" style="display:none">
  <table class="ltbl"><tbody>
  <% open.forEach(function(s){ var t=TIER[s.tier]||TIER.low; %>
    <tr>
      <td style="width:34px;font-weight:700">#<%= s.rank %></td>
      <td style="color:<%= t.color %>;width:16px"><%= t.dot %></td>
      <td><%= s.name %></td>
      <td style="width:80px"><strong><%= money(s.payout_max_cents) %></strong></td>
      <td class="<%= s.days===0?'today':'' %>" style="width:110px;color:var(--text-dim)"><%= deadlinePill(s) %></td>
      <td style="width:150px;text-align:right">
        <% if (s.no_claim_required){ %><span class="pill">automatic</span><% } else { %><button class="btn-go go" data-id="<%= s.id %>" data-name="<%= s.name %>" style="padding:5px 10px">Go →</button><% } %>
      </td>
    </tr>
  <% }) %>
  </tbody></table>
</section>

<section id="v-table" style="display:none">
  <table class="gtbl" id="gtbl">
    <thead><tr>
      <th data-k="rank" data-num>#</th><th data-k="priority" data-num>Prio</th>
      <th data-k="rv" data-num>💰Val</th><th data-k="ru" data-num>⏰Urg</th><th data-k="re" data-num>⚡Ease</th><th data-k="rw" data-num>🎯Win</th>
      <th data-k="payout" data-num>Payout</th><th data-k="days" data-num>Deadline</th><th data-k="proof">Proof</th><th data-k="name">Claim</th><th>Go</th>
    </tr></thead>
    <tbody>
    <% open.forEach(function(s){ var t=TIER[s.tier]||TIER.low; %>
      <tr data-rank="<%= s.rank %>" data-priority="<%= s.priority %>" data-rv="<%= s.ratings.value %>" data-ru="<%= s.ratings.urgency %>" data-re="<%= s.ratings.ease %>" data-rw="<%= s.ratings.winlikelihood %>" data-payout="<%= s.payout_max_cents||0 %>" data-days="<%= s.days==null?99999:s.days %>" data-proof="<%= proofLabel(s) %>" data-name="<%= s.name %>">
        <td style="font-weight:700">#<%= s.rank %></td><td><%= s.priority %></td>
        <td><%= s.ratings.value %></td><td><%= s.ratings.urgency %></td><td><%= s.ratings.ease %></td><td><%= s.ratings.winlikelihood %></td>
        <td><strong><%= money(s.payout_max_cents) %></strong></td>
        <td class="<%= s.days===0?'today':'' %>"><%= deadlinePill(s) %></td>
        <td><%= proofLabel(s) %></td>
        <td><%= s.name %></td>
        <td><% if (s.no_claim_required){ %><span class="pill">auto</span><% } else { %><button class="btn-go go" data-id="<%= s.id %>" data-name="<%= s.name %>" style="padding:4px 9px">Go</button><% } %></td>
      </tr>
    <% }) %>
    </tbody>
  </table>
</section>

<% if (expired.length){ %>
<section style="margin-top:26px">
  <h2 style="font-size:15px;color:var(--text-dim)">Recently expired <span class="pill"><%= expired.length %></span></h2>
  <% expired.forEach(function(s){ %><div class="claim-meta">· <%= s.name %> — <%= money(s.payout_max_cents) %> — <%= deadlinePill(s) %></div><% }) %>
</section>
<% } %>

<!-- prep modal -->
<div class="backdrop" id="prep-bd">
  <div class="prep">
    <h3 id="prep-title">Claim it &amp; Go</h3>
    <p class="claim-meta" id="prep-note"></p>
    <div class="lane green"><h4>🟢 We'll fill this — verify &amp; paste</h4><div id="prep-green"></div></div>
    <div class="lane amber"><h4>🟡 Needs you (once)</h4>
      <div class="claim-meta">Claim ID / PIN (on your mailed notice — many claims file without it), claim-specific facts, and any proof upload go in on the portal.</div>
      <div id="prep-docs" style="margin-top:8px"></div>
    </div>
    <div class="lane red"><h4>🔴 Locked — by law</h4>
      <div class="claim-meta">The "under penalty of perjury I am a class member" attestation + Submit are <strong>yours</strong>. Nothing is submitted for you.</div>
    </div>
    <div style="display:flex;gap:10px;margin-top:16px">
      <a id="prep-open" href="#" target="_blank" rel="noopener" class="btn-go" style="text-decoration:none;display:inline-block">Open claim portal ↗</a>
      <button onclick="document.getElementById('prep-bd').style.display='none'" style="background:transparent;color:var(--text-dim);border:1px solid rgba(255,255,255,.15);border-radius:8px;padding:8px 14px;cursor:pointer">Close</button>
    </div>
  </div>
</div>

<script>
(function(){
  // view toggle (persisted)
  var views=['grid','list','table']; var cur=localStorage.setlView||'grid';
  function show(v){ cur=v; localStorage.setlView=v; views.forEach(function(x){ document.getElementById('v-'+x).style.display = x===v?'':'none'; }); document.querySelectorAll('.views button').forEach(function(b){ b.classList.toggle('on', b.dataset.v===v); }); }
  document.querySelectorAll('.views button').forEach(function(b){ b.onclick=function(){ show(b.dataset.v); }; });
  show(cur);

  // sortable table
  var gt=document.getElementById('gtbl');
  if(gt){ gt.querySelectorAll('th[data-k]').forEach(function(th){ var asc=true; th.onclick=function(){ var k=th.dataset.k,num=th.hasAttribute('data-num'); var tb=gt.tBodies[0]; var rows=[].slice.call(tb.rows);
    rows.sort(function(a,b){ var x=a.dataset[k],y=b.dataset[k]; if(num){x=+x;y=+y;} else {x=(x||'').toLowerCase();y=(y||'').toLowerCase();} return (x<y?-1:x>y?1:0)*(asc?1:-1); });
    asc=!asc; rows.forEach(function(r){ tb.appendChild(r); }); }; }); }

  // Claim it & Go
  function esc(s){ return (s||'').replace(/[&<>"]/g,function(c){return{'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c];}); }
  function kv(k,v){ if(!v) return ''; return '<div class="kv"><span class="k">'+esc(k)+'</span><span class="v">'+esc(v)+'</span><button onclick="navigator.clipboard.writeText(this.previousSibling.textContent)">copy</button></div>'; }
  function showPrep(title, note, fields, screenshot){
    document.getElementById('prep-title').textContent=title;
    document.getElementById('prep-note').textContent=note||'';
    var g=document.getElementById('prep-green');
    if(screenshot){ g.innerHTML='<img src="'+screenshot+'?t='+Date.now()+'" style="width:100%;border-radius:8px;border:1px solid rgba(255,255,255,.1)"><div class="claim-meta" style="margin-top:6px">↑ what was typed into your Chrome. Switch to Chrome to review &amp; submit.</div>'; }
    else if(fields){ var f=fields; g.innerHTML=kv('First name',f.first_name)+kv('Last name',f.last_name)+kv('Street',f.street)+kv('City',f.city)+kv('State',f.state)+kv('Zip',f.zip)+kv('Email',f.email)+kv('Phone',f.phone)+kv('Payment',f.payment_method+' → '+f.paypal_email); }
    document.getElementById('prep-open').style.display='none';
    document.getElementById('prep-bd').style.display='flex';
  }
  function loadDocs(id){
    var box=document.getElementById('prep-docs'); box.innerHTML='<span class="claim-meta">🔎 searching your email for backup docs…</span>';
    fetch('/api/settlements/'+id+'/docs').then(function(r){return r.json();}).then(function(j){
      if(!j.ok){ box.innerHTML='<span class="claim-meta">📎 doc search needs George configured (GEORGE_BASIC_AUTH).</span>'; return; }
      var html='';
      (j.groups||[]).forEach(function(g){ if(!g.count) return;
        html+='<div style="margin:6px 0"><b style="font-size:12px">📎 '+g.label+' ('+g.count+')</b>';
        g.docs.forEach(function(d){ html+='<div class="kv"><span class="v" style="font-weight:400">'+ (d.subject||'').replace(/[<>]/g,'') +'</span><a class="k" style="color:#2d5bff" href="https://mail.google.com/mail/u/0/#search/rfc822msgid" target="_blank">'+(d.date||'').slice(0,16)+'</a></div>'; });
        html+='</div>';
      });
      box.innerHTML = html || '<span class="claim-meta">📎 no backup docs found in email for this claim.</span>';
    }).catch(function(){ box.innerHTML='<span class="claim-meta">📎 doc search error.</span>'; });
  }
  document.querySelectorAll('.go').forEach(function(btn){ btn.onclick=function(){
    var id=btn.dataset.id, name=btn.dataset.name||'Claim';
    btn.disabled=true; btn.textContent='🤖 auto-filling in Chrome…';
    // 1) live openclaw auto-fill (zero-click)
    fetch('/api/settlements/'+id+'/run',{method:'POST'}).then(function(r){return r.json();}).then(function(j){
      if(j && j.ok){ btn.disabled=false; btn.textContent='✅ Filled — review in Chrome';
        showPrep(name, 'Auto-filled '+((j.filled||[]).length)+' fields in your Chrome ('+(j.title||'')+'). Switch to Chrome, verify, check the attestation & submit. Nothing was submitted for you.', null, j.screenshot); loadDocs(id); return; }
      // 2) fallback: openclaw not up -> paste card
      fetch('/api/settlements/'+id+'/claim-and-go',{method:'POST'}).then(function(r){return r.json();}).then(function(k){
        btn.disabled=false; btn.textContent='✅ Claim it & Go';
        if(k.error){ alert(k.error); return; }
        showPrep(name, k.note||'', k.fields, null); loadDocs(id);
        var op=document.getElementById('prep-open'); if(k.mode_url){ op.href=k.mode_url; op.style.display=''; window.open(k.mode_url,'_blank','noopener'); }
      });
    }).catch(function(){ btn.disabled=false; btn.textContent='✅ Claim it & Go'; alert('error'); });
  }; });
})();
</script>
<%- include('partials/footer') %>