|
|
const venuesEl = document.getElementById('venues'); |
|
|
const seedBtn = document.getElementById('seed'); |
|
|
const addBtn = document.getElementById('v-add'); |
|
|
const nameIn = document.getElementById('v-name'); |
|
|
const slugIn = document.getElementById('v-slug'); |
|
|
const makeBtn = document.getElementById('session-make'); |
|
|
const slugSessionIn = document.getElementById('session-slug'); |
|
|
const qrArea = document.getElementById('qr-area'); |
|
|
const crmSee = document.getElementById('crm-see'); |
|
|
const crmJson = document.getElementById('crm-json'); |
|
|
|
|
|
async function listVenues(){ |
|
|
const r = await fetch('/api/admin/venues', {cache:'no-store'}); |
|
|
const items = await r.json(); |
|
|
if (!Array.isArray(items) || items.length === 0){ |
|
|
venuesEl.textContent = '(登録なし)'; |
|
|
return; |
|
|
} |
|
|
venuesEl.innerHTML = '<ul>' + items.map(v => `<li><code>${v.slug}</code> — ${v.name}</li>`).join('') + '</ul>'; |
|
|
} |
|
|
|
|
|
async function seedDemo(){ |
|
|
const r = await fetch('/api/admin/seed', {method:'POST'}); |
|
|
const j = await r.json(); |
|
|
alert(j.created ? 'デモ店舗を作成しました' : 'デモ店舗は既に存在します'); |
|
|
await listVenues(); |
|
|
} |
|
|
|
|
|
async function addVenue(){ |
|
|
const name = nameIn.value.trim(); |
|
|
const slug = slugIn.value.trim(); |
|
|
if (!name || !slug){ alert('店名とslugを入力してください'); return; } |
|
|
const r = await fetch('/api/admin/venues', { |
|
|
method:'POST', |
|
|
headers:{'Content-Type':'application/json'}, |
|
|
body: JSON.stringify({name, slug}) |
|
|
}); |
|
|
if (r.ok){ nameIn.value=''; slugIn.value=''; await listVenues(); } |
|
|
else { const e = await r.json().catch(()=>({})); alert('登録失敗: ' + (e.detail || r.status)); } |
|
|
} |
|
|
|
|
|
async function fetchQRObjectURL(sessionId){ |
|
|
const url = `/qrcode/${sessionId}`; |
|
|
const r = await fetch(url, {cache:'no-store'}); |
|
|
if (!r.ok) throw new Error(`QR取得失敗: ${r.status}`); |
|
|
const blob = await r.blob(); |
|
|
return URL.createObjectURL(blob); |
|
|
} |
|
|
|
|
|
async function makeSession(){ |
|
|
const slug = slugSessionIn.value.trim(); |
|
|
if (!slug){ alert('店舗slugを入力してください'); return; } |
|
|
const r = await fetch('/api/checkin/session', { |
|
|
method:'POST', |
|
|
headers:{'Content-Type':'application/json'}, |
|
|
body: JSON.stringify({venue_slug: slug}) |
|
|
}); |
|
|
const j = await r.json(); |
|
|
if (!r.ok){ alert('発行失敗: ' + (j.detail || r.status)); return; } |
|
|
|
|
|
const checkinUrl = `/checkin/${j.session_id}`; |
|
|
|
|
|
|
|
|
let qrObjectUrl = ''; |
|
|
try { |
|
|
qrObjectUrl = await fetchQRObjectURL(j.session_id); |
|
|
} catch (e) { |
|
|
console.error(e); |
|
|
} |
|
|
|
|
|
qrArea.innerHTML = ` |
|
|
<div style="display:flex;gap:16px;align-items:center;flex-wrap:wrap"> |
|
|
<img src="${qrObjectUrl || ''}" alt="QR" style="width:180px;height:180px;background:#fff;padding:8px;border-radius:8px;object-fit:contain;border:1px solid #333"/> |
|
|
<div> |
|
|
<div>セッション: <code>${j.session_id}</code></div> |
|
|
<div style="margin-top:8px"> |
|
|
<a class="btn" target="_blank" href="${checkinUrl}">チェックインページを開く</a> |
|
|
</div> |
|
|
<p class="muted" style="margin-top:8px"> |
|
|
画像が表示されない場合は、右のボタンでページを開き、URLを手元のQR生成アプリに貼ってください。 |
|
|
</p> |
|
|
</div> |
|
|
</div> |
|
|
<p class="muted">※ QRは5分で失効します。必要に応じて再発行してください。</p> |
|
|
`; |
|
|
} |
|
|
|
|
|
async function seeCRM(){ |
|
|
const r = await fetch('/api/crm/segments', {cache:'no-store'}); |
|
|
const j = await r.json(); |
|
|
crmJson.textContent = JSON.stringify(j, null, 2); |
|
|
} |
|
|
|
|
|
seedBtn.onclick = seedDemo; |
|
|
addBtn.onclick = addVenue; |
|
|
makeBtn.onclick = makeSession; |
|
|
crmSee.onclick = seeCRM; |
|
|
|
|
|
listVenues(); |
|
|
|