← back to Rentv

admin/desk-admin.html

121 lines

<!doctype html><html lang="en"><head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>RENTV · Sublease Desk — Admin Entry</title>
<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@800&family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<style>
  :root{--red:#c8102e;--line:#232a32}*{box-sizing:border-box;margin:0}
  body{background:#0d1014;color:#e7ecf1;font-family:'Inter',system-ui,sans-serif;font-size:14px}
  a{color:#7cc0ff;text-decoration:none}
  .top{position:sticky;top:0;z-index:9;display:flex;align-items:center;justify-content:space-between;height:52px;padding:0 16px;background:#0b0e12;border-bottom:1px solid var(--line)}
  .brand{font-family:'Playfair Display',serif;font-weight:800;font-size:19px}.brand span{color:var(--red)}
  .brand small{font-family:'Inter';font-weight:700;font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:#8b939b;margin-left:10px}
  .wrap{max-width:1100px;margin:0 auto;padding:24px 18px 80px;display:grid;grid-template-columns:minmax(0,420px) 1fr;gap:26px}
  @media(max-width:820px){.wrap{grid-template-columns:1fr}}
  h2{font-size:13px;letter-spacing:.1em;text-transform:uppercase;color:#8b939b;margin:0 0 12px}
  form{background:#11161c;border:1px solid var(--line);border-radius:14px;padding:18px}
  .fld{margin-bottom:12px}.fld label{display:block;font-size:11px;text-transform:uppercase;letter-spacing:.05em;color:#8b939b;margin-bottom:5px}
  .fld label .req{color:var(--red)}
  input,select,textarea{width:100%;background:#0d1116;border:1px solid var(--line);border-radius:9px;color:#e7ecf1;padding:10px 12px;font:inherit}
  input:focus,select:focus,textarea:focus{outline:none;border-color:var(--red)}
  .row2{display:grid;grid-template-columns:1fr 1fr;gap:10px}
  .btn{background:var(--red);border:0;color:#fff;font-weight:800;padding:12px 18px;border-radius:10px;cursor:pointer;font-size:14px;width:100%;margin-top:6px}.btn:hover{filter:brightness(1.1)}
  .note{font-size:12px;color:#6b747d;margin-top:12px;line-height:1.6}
  .msg{margin-top:12px;font-size:13px;font-weight:700;display:none}.msg.ok{display:block;color:#37d67a}.msg.err{display:block;color:#ff6b6b}
  .list{min-width:0}
  .card{background:#11161c;border:1px solid var(--line);border-radius:12px;padding:14px 16px;margin-bottom:12px}
  .card .hd{display:flex;justify-content:space-between;align-items:flex-start;gap:10px}
  .card .addr{font-weight:800;font-size:16px}
  .card .sub{color:#9aa4ad;font-size:13px;margin-top:2px}
  .card .facts{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px}
  .chip{background:#0d1116;border:1px solid var(--line);border-radius:7px;padding:5px 9px;font-size:12px;color:#c3cbd3}
  .chip b{color:#e7ecf1}
  .when{background:#0d1116;border:1px solid var(--line);border-radius:7px;padding:5px 9px;font-size:11.5px;color:#8b939b}
  .wd{background:none;border:1px solid #3a2a2e;color:#ff8b8b;border-radius:8px;padding:6px 11px;font-weight:700;font-size:12px;cursor:pointer;white-space:nowrap}.wd:hover{background:#2a1418;border-color:var(--red)}
  .empty{color:#6b747d;font-size:13px;padding:24px 4px;line-height:1.6}
</style></head>
<body>
<header class="top">
  <a class="brand" href="/desk">REN<span>TV</span><small>Sublease Desk · Admin</small></a>
  <a href="/desk" style="font-weight:700;font-size:13px">← Back to Desk</a>
</header>
<div class="wrap">
  <div>
    <h2>Add a sublease listing</h2>
    <form id="f" autocomplete="off">
      <div class="fld"><label>Address <span class="req">*</span></label><input name="address" required placeholder="6100 Wilshire Blvd, 12th Fl"></div>
      <div class="row2">
        <div class="fld"><label>City</label><input name="city" placeholder="Los Angeles"></div>
        <div class="fld"><label>Submarket</label><input name="submarket" placeholder="Miracle Mile"></div>
      </div>
      <div class="row2">
        <div class="fld"><label>Floor / Suite</label><input name="floor_suite" placeholder="Suite 1200"></div>
        <div class="fld"><label>SqFt</label><input name="sqft" type="number" min="0" placeholder="18500"></div>
      </div>
      <div class="row2">
        <div class="fld"><label>Asking rate ($/SF)</label><input name="asking_rate" type="number" step="0.01" min="0" placeholder="3.25"></div>
        <div class="fld"><label>Per</label><select name="rate_period"><option value="yr">/ year</option><option value="mo">/ month</option></select></div>
      </div>
      <div class="row2">
        <div class="fld"><label>Type</label><select name="lease_type"><option value="sublease">Sublease</option><option value="direct">Direct lease</option></select></div>
        <div class="fld"><label>Term (months)</label><input name="term_months" type="number" min="0" placeholder="36"></div>
      </div>
      <div class="row2">
        <div class="fld"><label>Available</label><input name="available_date" type="date"></div>
        <div class="fld"><label>Sublandlord (tenant)</label><input name="sublandlord" placeholder="Acme Media Corp"></div>
      </div>
      <div class="fld"><label>Listing brokerage</label><input name="firm_name" placeholder="CBRE"></div>
      <div class="row2">
        <div class="fld"><label>Broker name</label><input name="broker_name" placeholder="Jane Doe"></div>
        <div class="fld"><label>Broker phone</label><input name="broker_phone" placeholder="(310) 555-0100"></div>
      </div>
      <div class="fld"><label>Broker email</label><input name="broker_email" type="email" placeholder="jane@cbre.com"></div>
      <div class="fld"><label>Listing URL</label><input name="listing_url" type="url" placeholder="https://…"></div>
      <div class="fld"><label>Notes</label><textarea name="notes" rows="2" placeholder="Furnished, plug-and-play, 40 workstations…"></textarea></div>
      <button class="btn" type="submit">Add listing</button>
      <div class="msg" id="msg"></div>
      <div class="note">Facts-only, real deals from the RENTV broker network. Each entry appears live on the <a href="/desk">Intelligence Desk → Sublease</a> scope.</div>
    </form>
  </div>
  <div class="list">
    <h2>Current inventory (<span id="cnt">0</span>)</h2>
    <div id="rows"></div>
  </div>
</div>
<script>
const $=s=>document.getElementById(s);
const esc=s=>String(s==null?'':s).replace(/[&<>"]/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]));
const fmtWhen=d=>{try{return new Date(d).toLocaleString(undefined,{year:'numeric',month:'short',day:'numeric',hour:'numeric',minute:'2-digit'});}catch(e){return d;}};
const rate=x=>x.asking_rate?('$'+Number(x.asking_rate).toFixed(2)+'/'+(x.rate_period||'yr')+' SF'):'Rate on request';
async function load(){
  const r=await fetch('/api/sublease?status=active&limit=500',{credentials:'same-origin'}).then(r=>r.json()).catch(()=>({rows:[]}));
  const rows=r.rows||[];$('cnt').textContent=rows.length;
  $('rows').innerHTML=rows.length?rows.map(x=>`<div class="card"><div class="hd"><div><div class="addr">${esc(x.address)}</div><div class="sub">${esc([x.floor_suite,x.submarket,x.city].filter(Boolean).join(' · '))}</div></div><button class="wd" data-id="${x.id}">Withdraw</button></div>
    <div class="facts">
      ${x.sqft?`<span class="chip"><b>${Number(x.sqft).toLocaleString()}</b> SF</span>`:''}
      <span class="chip">${esc(rate(x))}</span>
      ${x.term_months?`<span class="chip"><b>${x.term_months}</b> mo</span>`:''}
      ${x.sublandlord?`<span class="chip">Sublandlord: <b>${esc(x.sublandlord)}</b></span>`:''}
      ${x.firm_name?`<span class="chip">${esc(x.firm_name)}</span>`:''}
      <span class="when" title="${esc(x.created_at)}">🕓 ${esc(fmtWhen(x.created_at))}</span>
    </div></div>`).join(''):'<div class="empty">No listings yet. Add the first one on the left — it goes live on the desk immediately.</div>';
  $('rows').querySelectorAll('.wd').forEach(b=>b.onclick=async()=>{
    if(!confirm('Withdraw this listing from the desk?'))return;
    await fetch('/api/admin/sublease/'+b.dataset.id,{method:'DELETE',credentials:'same-origin'});load();
  });
}
$('f').onsubmit=async e=>{
  e.preventDefault();const m=$('msg');m.className='msg';m.textContent='';
  // Numeric inputs must go up as numbers, not FormData strings, so the backend stores them cleanly.
  const NUMK=['sqft','asking_rate','term_months','lat','lng'];
  const body={};new FormData(e.target).forEach((v,k)=>{if(String(v).trim()!=='')body[k]=NUMK.includes(k)?Number(v):v;});
  try{
    const r=await fetch('/api/admin/sublease',{method:'POST',headers:{'Content-Type':'application/json'},credentials:'same-origin',body:JSON.stringify(body)});
    const j=await r.json();
    if(r.ok&&j.ok){m.className='msg ok';m.textContent='✓ Added — live on the desk.';e.target.reset();load();}
    else{m.className='msg err';m.textContent='✕ '+(j.error||('HTTP '+r.status));}
  }catch(err){m.className='msg err';m.textContent='✕ '+err.message;}
};
load();
</script>
</body></html>