← 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 & 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 & 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 & Go</h3>
<p class="claim-meta" id="prep-note"></p>
<div class="lane green"><h4>🟢 We'll fill this — verify & 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{'&':'&','<':'<','>':'>','"':'"'}[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 & 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') %>