← back to Costa Rica
public/admin.html
73 lines
<!doctype html>
<html lang="en"><head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
<title>CR Marketplace — Admin</title>
<style>
:root { --g:#0a7d55; --ink:#1a2b22; --mut:#6b756e; --line:#e2e5e0; }
* { box-sizing:border-box; } body { margin:0; font:15px/1.45 -apple-system,Segoe UI,Roboto,sans-serif; background:#f6f7f5; color:var(--ink); }
header { background:var(--g); color:#fff; padding:14px 20px; display:flex; align-items:center; gap:16px; }
header h1 { font-size:18px; margin:0; } .tabs { margin-left:auto; display:flex; gap:8px; }
.tab { background:rgba(255,255,255,.15); color:#fff; border:0; padding:8px 14px; border-radius:8px; cursor:pointer; }
.tab.on { background:#fff; color:var(--g); font-weight:700; }
.kpis { display:flex; gap:12px; flex-wrap:wrap; padding:16px 20px; }
.kpi { background:#fff; border:1px solid var(--line); border-radius:12px; padding:12px 16px; min-width:120px; }
.kpi b { display:block; font-size:22px; color:var(--g); } .kpi span { color:var(--mut); font-size:12px; }
main { padding:0 20px 40px; } .grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(300px,1fr)); gap:12px; }
.card { background:#fff; border:1px solid var(--line); border-radius:12px; padding:14px; }
.card h3 { margin:0 0 4px; font-size:15px; } .meta { color:var(--mut); font-size:13px; }
.when { display:inline-block; margin-top:8px; background:#eef5f1; color:#2b5c47; border-radius:6px; padding:2px 8px; font-size:12px; }
.badge { float:right; font-weight:700; text-transform:capitalize; font-size:13px; }
.row { display:flex; gap:8px; margin-top:10px; } button.act { flex:1; border:0; border-radius:8px; padding:8px; cursor:pointer; color:#fff; }
.ok { background:var(--g); } .no { background:#a33; } .hidden { display:none; }
.total { font-weight:800; color:var(--g); margin-top:6px; }
</style></head>
<body>
<header><h1>🇨🇷 CR Marketplace — Admin</h1>
<div class="tabs">
<button class="tab on" data-t="claims" onclick="tab('claims')">Host Claims</button>
<button class="tab" data-t="bookings" onclick="tab('bookings')">Bookings</button>
</div>
</header>
<div style="background:#fff4d6;border-bottom:1px solid #e6d9a8;color:#8a6d1a;padding:8px 20px;font-size:13px;font-weight:600">🧪 SANDBOX — all bookings/users/revenue below are TEST data from build verification, not real customers.</div>
<div class="kpis" id="kpis"></div>
<main><div class="grid" id="grid"></div></main>
<script>
const money=(m,c)=>`${c} ${(m/100).toFixed(2)}`;
const fmt=t=>new Date(t).toLocaleString(undefined,{year:'numeric',month:'short',day:'numeric',hour:'numeric',minute:'2-digit'});
let cur='claims';
async function j(u,o){const r=await fetch(u,o);return r.json();}
async function loadKpis(){const {stats:s}=await j('/api/admin/stats');document.getElementById('kpis').innerHTML=
[['Bookings',s.bookings],['Confirmed',s.confirmed],['GMV',money(s.gmv_minor,'USD')],['Revenue',money(s.revenue_minor,'USD')],
['Pending claims',s.pending_claims],['Bookable',s.bookable],['Hosts',s.hosts]]
.map(([k,v])=>`<div class="kpi"><b>${v}</b><span>${k}</span></div>`).join('');}
async function tab(t){cur=t;document.querySelectorAll('.tab').forEach(b=>b.classList.toggle('on',b.dataset.t===t));render();}
async function render(){
const g=document.getElementById('grid');
if(cur==='claims'){
const {claims}=await j('/api/admin/claims?status=all');
g.innerHTML=claims.length?claims.map(c=>`<div class="card">
<span class="badge" style="color:${c.claim_status==='approved'?'#0a7d55':c.claim_status==='rejected'?'#a33':'#b8860b'}">${c.claim_status}</span>
<h3>${c.place_name}</h3>
<div class="meta">${c.legal_name||c.email} · ${c.country} · céd ${c.cedula||'—'}</div>
<div class="when" title="${c.created_at}">🕓 ${fmt(c.created_at)}</div>
${c.claim_status==='pending'?`<div class="row">
<button class="act ok" onclick="decide(${c.place_id},${c.host_id},'approved')">Approve</button>
<button class="act no" onclick="decide(${c.place_id},${c.host_id},'rejected')">Reject</button></div>`:''}
</div>`).join(''):'<p class="meta">No claims yet.</p>';
} else {
const {bookings}=await j('/api/admin/bookings');
g.innerHTML=bookings.length?bookings.map(b=>`<div class="card">
<span class="badge" style="color:${b.status==='confirmed'?'#0a7d55':b.status==='cancelled'?'#a33':'#b8860b'}">${b.status}</span>
<h3>${b.place_name}</h3>
<div class="meta">${b.code} · ${b.traveler_email} · ${b.guests}g</div>
<div class="meta">${b.check_in||''} → ${b.check_out||''}</div>
<div class="total">${money(b.total,b.currency)} <span class="meta">(host ${money(b.host_payout,b.currency)} · fee ${money(b.platform_fee,b.currency)})</span></div>
<div class="when" title="${b.created_at}">🕓 ${fmt(b.created_at)}</div>
</div>`).join(''):'<p class="meta">No bookings yet.</p>';
}
}
async function decide(p,h,d){await j(`/api/admin/claims/${p}/${h}`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({decision:d})});loadKpis();render();}
loadKpis();render();
</script>
</body></html>