675 lines
21 KiB
HTML
675 lines
21 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="el">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Αγορά — Grepolis Remote</title>
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700;800&display=swap" rel="stylesheet">
|
||
<style>
|
||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||
|
||
:root {
|
||
--bg: #0f0f1a;
|
||
--panel: #181824;
|
||
--border: #2a2a3e;
|
||
--purple: #b482dc;
|
||
--purple-dim: rgba(180,130,220,0.12);
|
||
--gold: #c8a44a;
|
||
--green: #4acc64;
|
||
--red: #e05555;
|
||
--yellow: #e0b847;
|
||
--text: #e0e0e0;
|
||
--muted: #666;
|
||
}
|
||
|
||
body {
|
||
background: var(--bg);
|
||
font-family: 'Inter', sans-serif;
|
||
color: var(--text);
|
||
min-height: 100vh;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
/* ── Top bar ── */
|
||
.topbar {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 1rem;
|
||
padding: 0.9rem 1.5rem;
|
||
background: #13131f;
|
||
border-bottom: 1px solid var(--border);
|
||
}
|
||
.topbar-title {
|
||
font-size: 1.2rem;
|
||
font-weight: 800;
|
||
color: var(--purple);
|
||
flex: 1;
|
||
}
|
||
.topbar a {
|
||
color: var(--muted);
|
||
text-decoration: none;
|
||
font-size: 0.82rem;
|
||
transition: color .2s;
|
||
}
|
||
.topbar a:hover { color: var(--text); }
|
||
.status-dot {
|
||
width: 8px; height: 8px;
|
||
border-radius: 50%;
|
||
background: var(--muted);
|
||
display: inline-block;
|
||
margin-right: 6px;
|
||
}
|
||
.status-dot.online { background: var(--green); box-shadow: 0 0 6px var(--green); }
|
||
|
||
/* ── Main layout ── */
|
||
.main {
|
||
display: grid;
|
||
grid-template-columns: 300px 1fr;
|
||
grid-template-rows: 1fr auto;
|
||
gap: 0;
|
||
flex: 1;
|
||
overflow: hidden;
|
||
height: calc(100vh - 52px);
|
||
}
|
||
|
||
/* ── Left panel ── */
|
||
.left-panel {
|
||
background: var(--panel);
|
||
border-right: 1px solid var(--border);
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
grid-row: 1 / 3;
|
||
}
|
||
.panel-header {
|
||
padding: 1rem 1.2rem 0.6rem;
|
||
font-size: 0.72rem;
|
||
font-weight: 700;
|
||
text-transform: uppercase;
|
||
letter-spacing: .08em;
|
||
color: var(--muted);
|
||
border-bottom: 1px solid var(--border);
|
||
flex-shrink: 0;
|
||
}
|
||
.search-box {
|
||
padding: 0.7rem 1rem;
|
||
border-bottom: 1px solid var(--border);
|
||
flex-shrink: 0;
|
||
}
|
||
.search-box input {
|
||
width: 100%;
|
||
background: #1e1e2e;
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
padding: 6px 10px;
|
||
color: var(--text);
|
||
font-size: 0.82rem;
|
||
outline: none;
|
||
transition: border-color .2s;
|
||
}
|
||
.search-box input:focus { border-color: var(--purple); }
|
||
|
||
.town-list {
|
||
overflow-y: auto;
|
||
flex: 1;
|
||
}
|
||
.town-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.6rem;
|
||
padding: 0.65rem 1.1rem;
|
||
border-bottom: 1px solid #1e1e2e;
|
||
cursor: pointer;
|
||
transition: background .15s;
|
||
position: relative;
|
||
}
|
||
.town-row:hover { background: #1e1e30; }
|
||
.town-row.active { background: var(--purple-dim); border-left: 3px solid var(--purple); }
|
||
.town-row .t-name { font-size: 0.84rem; font-weight: 600; flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||
.town-row .t-dots { display: flex; gap: 4px; }
|
||
.t-dot {
|
||
width: 8px; height: 8px;
|
||
border-radius: 50%;
|
||
background: var(--muted);
|
||
}
|
||
.t-dot.available { background: var(--green); }
|
||
.t-dot.cooldown { background: var(--yellow); }
|
||
.t-dot.unavailable { background: #333; }
|
||
|
||
/* ── Right panel ── */
|
||
.right-panel {
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
}
|
||
.cards-area {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
padding: 1.5rem;
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 1.2rem;
|
||
align-content: start;
|
||
}
|
||
.no-selection {
|
||
grid-column: 1 / -1;
|
||
text-align: center;
|
||
color: var(--muted);
|
||
font-size: 0.9rem;
|
||
padding: 4rem 0;
|
||
}
|
||
|
||
/* ── Celebration cards ── */
|
||
.cel-card {
|
||
background: #1e1e30;
|
||
border: 1px solid var(--border);
|
||
border-radius: 14px;
|
||
padding: 1.4rem;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 0.9rem;
|
||
transition: border-color .2s;
|
||
}
|
||
.cel-card.available { border-color: rgba(180,130,220,0.35); }
|
||
.cel-card.cooldown { border-color: rgba(224,184,71,0.35); }
|
||
.cel-card-title {
|
||
font-size: 1rem;
|
||
font-weight: 700;
|
||
color: var(--purple);
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
}
|
||
.cel-card.cooldown .cel-card-title { color: var(--yellow); }
|
||
|
||
.cost-row {
|
||
display: flex;
|
||
gap: 0.8rem;
|
||
flex-wrap: wrap;
|
||
}
|
||
.cost-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
font-size: 0.78rem;
|
||
font-weight: 600;
|
||
}
|
||
.cost-item.ok { color: var(--green); }
|
||
.cost-item.bad { color: var(--red); }
|
||
.cost-item.neutral { color: var(--text); }
|
||
|
||
.reason-box {
|
||
font-size: 0.78rem;
|
||
color: var(--yellow);
|
||
padding: 6px 10px;
|
||
background: rgba(224,184,71,0.08);
|
||
border-radius: 8px;
|
||
border: 1px solid rgba(224,184,71,0.2);
|
||
line-height: 1.4;
|
||
}
|
||
.reason-box.error {
|
||
color: var(--red);
|
||
background: rgba(224,85,85,0.08);
|
||
border-color: rgba(224,85,85,0.2);
|
||
}
|
||
|
||
.cel-btn {
|
||
padding: 9px 16px;
|
||
border-radius: 9px;
|
||
border: none;
|
||
cursor: pointer;
|
||
font-size: 0.84rem;
|
||
font-weight: 700;
|
||
transition: all .2s;
|
||
align-self: flex-start;
|
||
}
|
||
.cel-btn.primary {
|
||
background: var(--purple);
|
||
color: #fff;
|
||
}
|
||
.cel-btn.primary:hover { background: #c99ef0; transform: translateY(-1px); }
|
||
.cel-btn:disabled {
|
||
background: #2a2a3e;
|
||
color: var(--muted);
|
||
cursor: not-allowed;
|
||
transform: none;
|
||
}
|
||
|
||
/* ── Log panel ── */
|
||
.log-panel {
|
||
border-top: 1px solid var(--border);
|
||
background: #13131f;
|
||
flex-shrink: 0;
|
||
}
|
||
.log-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 0.6rem 1.2rem;
|
||
border-bottom: 1px solid var(--border);
|
||
cursor: pointer;
|
||
user-select: none;
|
||
}
|
||
.log-header span { font-size: 0.72rem; font-weight: 700; text-transform: uppercase; letter-spacing: .08em; color: var(--muted); }
|
||
.log-body {
|
||
overflow-y: auto;
|
||
max-height: 170px;
|
||
padding: 0.5rem 1rem;
|
||
font-family: 'Courier New', monospace;
|
||
font-size: 0.77rem;
|
||
display: none;
|
||
}
|
||
.log-body.open { display: block; }
|
||
.log-entry {
|
||
display: flex;
|
||
gap: 0.8rem;
|
||
padding: 3px 0;
|
||
border-bottom: 1px solid #1a1a28;
|
||
align-items: center;
|
||
}
|
||
.log-time { color: var(--muted); min-width: 55px; }
|
||
.log-town { color: var(--purple); min-width: 140px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||
.log-type { color: var(--text); min-width: 150px; }
|
||
.log-status.success { color: var(--green); }
|
||
.log-status.failed { color: var(--red); }
|
||
.log-status.pending { color: var(--yellow); }
|
||
.log-msg { color: var(--muted); font-size: 0.72rem; flex: 1; }
|
||
.log-empty { color: var(--muted); padding: 0.6rem 0; font-size: 0.8rem; }
|
||
|
||
/* ── Confirm Modal ── */
|
||
.modal-overlay {
|
||
display: none;
|
||
position: fixed; inset: 0;
|
||
background: rgba(0,0,0,0.7);
|
||
z-index: 100;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
.modal-overlay.open { display: flex; }
|
||
.modal-box {
|
||
background: #1e1e30;
|
||
border: 1px solid var(--purple);
|
||
border-radius: 16px;
|
||
padding: 2rem;
|
||
min-width: 340px;
|
||
max-width: 420px;
|
||
box-shadow: 0 20px 60px rgba(180,130,220,0.2);
|
||
}
|
||
.modal-title {
|
||
font-size: 1.1rem;
|
||
font-weight: 800;
|
||
color: var(--purple);
|
||
margin-bottom: 1rem;
|
||
}
|
||
.modal-row {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
font-size: 0.85rem;
|
||
padding: 5px 0;
|
||
border-bottom: 1px solid var(--border);
|
||
}
|
||
.modal-row span:first-child { color: var(--muted); }
|
||
.modal-row span:last-child { font-weight: 600; }
|
||
.modal-actions {
|
||
display: flex;
|
||
gap: 0.8rem;
|
||
margin-top: 1.4rem;
|
||
justify-content: flex-end;
|
||
}
|
||
.btn-cancel {
|
||
padding: 8px 18px;
|
||
border-radius: 8px;
|
||
background: #2a2a3e;
|
||
border: 1px solid var(--border);
|
||
color: var(--text);
|
||
cursor: pointer;
|
||
font-size: 0.84rem;
|
||
font-weight: 600;
|
||
}
|
||
.btn-cancel:hover { border-color: var(--purple); }
|
||
.btn-confirm {
|
||
padding: 8px 20px;
|
||
border-radius: 8px;
|
||
background: var(--purple);
|
||
border: none;
|
||
color: #fff;
|
||
cursor: pointer;
|
||
font-size: 0.84rem;
|
||
font-weight: 700;
|
||
transition: background .2s;
|
||
}
|
||
.btn-confirm:hover { background: #c99ef0; }
|
||
.btn-confirm:disabled { background: #444; color: var(--muted); cursor: not-allowed; }
|
||
|
||
.toast {
|
||
position: fixed;
|
||
bottom: 1.5rem;
|
||
right: 1.5rem;
|
||
padding: 10px 18px;
|
||
border-radius: 10px;
|
||
font-size: 0.84rem;
|
||
font-weight: 600;
|
||
z-index: 200;
|
||
opacity: 0;
|
||
transition: opacity .3s;
|
||
pointer-events: none;
|
||
}
|
||
.toast.show { opacity: 1; }
|
||
.toast.ok { background: rgba(74,204,100,0.15); border: 1px solid var(--green); color: var(--green); }
|
||
.toast.err { background: rgba(224,85,85,0.15); border: 1px solid var(--red); color: var(--red); }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<!-- Top Bar -->
|
||
<div class="topbar">
|
||
<div class="topbar-title">🎭 Αγορά</div>
|
||
<span id="online-indicator"><span class="status-dot" id="status-dot"></span><span id="status-text">Φόρτωση...</span></span>
|
||
<a href="/player/{{ player_id }}/{{ world_id }}">← Hub</a>
|
||
</div>
|
||
|
||
<!-- Main Layout -->
|
||
<div class="main">
|
||
|
||
<!-- Left: Town List -->
|
||
<div class="left-panel">
|
||
<div class="panel-header">Πόλεις (<span id="town-count">0</span>)</div>
|
||
<div class="search-box">
|
||
<input type="text" id="search" placeholder="Αναζήτηση πόλης...">
|
||
</div>
|
||
<div class="town-list" id="town-list">
|
||
<div style="padding:1.5rem;color:#555;font-size:0.82rem">Φόρτωση...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Right: Cards + Log -->
|
||
<div class="right-panel">
|
||
|
||
<div class="cards-area" id="cards-area">
|
||
<div class="no-selection">⬅ Επέλεξε πόλη για να δεις τις διαθέσιμες εορτές</div>
|
||
</div>
|
||
|
||
<!-- Log Panel -->
|
||
<div class="log-panel">
|
||
<div class="log-header" onclick="toggleLog()">
|
||
<span>📜 Αρχείο Αγοράς</span>
|
||
<span id="log-toggle-icon">▲</span>
|
||
</div>
|
||
<div class="log-body open" id="log-body">
|
||
<div class="log-empty" id="log-empty">Δεν υπάρχουν εγγραφές ακόμα.</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Confirm Modal -->
|
||
<div class="modal-overlay" id="confirm-modal">
|
||
<div class="modal-box">
|
||
<div class="modal-title" id="modal-title">Επιβεβαίωση Εορτής</div>
|
||
<div id="modal-rows"></div>
|
||
<div class="modal-actions">
|
||
<button class="btn-cancel" onclick="closeModal()">Ακύρωση</button>
|
||
<button class="btn-confirm" id="btn-confirm" onclick="fireCommand()">⚡ Εκτέλεση</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Toast -->
|
||
<div class="toast" id="toast"></div>
|
||
|
||
<script>
|
||
const PLAYER_ID = '{{ player_id }}';
|
||
const WORLD_ID = '{{ world_id }}';
|
||
|
||
const TYPE_LABELS = { party: 'Γιορτή πόλης 🎉', triumph: 'Παρέλαση θριάμβου ⚔️' };
|
||
const PARTY_COSTS = { wood: 15000, stone: 18000, iron: 15000 };
|
||
|
||
let agoraData = [];
|
||
let selectedId = null;
|
||
let pendingCmd = null; // { town, cel_type }
|
||
let logOpen = true;
|
||
|
||
// ── Fetch agora data ─────────────────────────────────────────────
|
||
async function fetchAgora() {
|
||
try {
|
||
const r = await fetch(`/dashboard/agora?player_id=${PLAYER_ID}&world_id=${WORLD_ID}`);
|
||
const d = await r.json();
|
||
agoraData = d.towns || [];
|
||
document.getElementById('town-count').textContent = agoraData.length;
|
||
renderTownList();
|
||
if (selectedId) renderCards(agoraData.find(t => t.town_id === selectedId));
|
||
} catch (e) { /* silent */ }
|
||
}
|
||
|
||
// ── Render town list ─────────────────────────────────────────────
|
||
function renderTownList() {
|
||
const q = document.getElementById('search').value.toLowerCase();
|
||
const list = document.getElementById('town-list');
|
||
const filtered = agoraData.filter(t => t.town_name.toLowerCase().includes(q));
|
||
|
||
if (!filtered.length) {
|
||
list.innerHTML = '<div style="padding:1rem;color:#555;font-size:0.82rem">Δεν βρέθηκαν πόλεις.</div>';
|
||
return;
|
||
}
|
||
|
||
list.innerHTML = filtered.map(t => `
|
||
<div class="town-row ${t.town_id === selectedId ? 'active' : ''}"
|
||
onclick="selectTown('${t.town_id}')">
|
||
<span class="t-name">${t.town_name}</span>
|
||
<span class="t-dots">
|
||
<span class="t-dot ${t.party.status}" title="Γιορτή πόλης: ${t.party.status}"></span>
|
||
<span class="t-dot ${t.triumph.status}" title="Παρέλαση: ${t.triumph.status}"></span>
|
||
</span>
|
||
</div>
|
||
`).join('');
|
||
}
|
||
|
||
function selectTown(townId) {
|
||
selectedId = townId;
|
||
renderTownList();
|
||
renderCards(agoraData.find(t => t.town_id === townId));
|
||
}
|
||
|
||
// ── Render celebration cards ─────────────────────────────────────
|
||
function renderCards(town) {
|
||
const area = document.getElementById('cards-area');
|
||
if (!town) {
|
||
area.innerHTML = '<div class="no-selection">⬅ Επέλεξε πόλη για να δεις τις διαθέσιμες εορτές</div>';
|
||
return;
|
||
}
|
||
|
||
area.innerHTML = `
|
||
${celebCard(town, 'party')}
|
||
${celebCard(town, 'triumph')}
|
||
`;
|
||
}
|
||
|
||
function celebCard(town, type) {
|
||
const cel = town[type];
|
||
const label = TYPE_LABELS[type];
|
||
|
||
let costsHtml = '';
|
||
if (type === 'party') {
|
||
const r = town.resources;
|
||
costsHtml = `
|
||
<div class="cost-row">
|
||
<span class="cost-item ${r.wood >= PARTY_COSTS.wood ? 'ok' : 'bad'}">🪵 ${fmt(r.wood)} / ${fmt(PARTY_COSTS.wood)}</span>
|
||
<span class="cost-item ${r.stone >= PARTY_COSTS.stone ? 'ok' : 'bad'}">🪨 ${fmt(r.stone)} / ${fmt(PARTY_COSTS.stone)}</span>
|
||
<span class="cost-item ${r.iron >= PARTY_COSTS.iron ? 'ok' : 'bad'}">⚙️ ${fmt(r.iron)} / ${fmt(PARTY_COSTS.iron)}</span>
|
||
</div>`;
|
||
} else {
|
||
const needed = 300;
|
||
costsHtml = `
|
||
<div class="cost-row">
|
||
<span class="cost-item ${town.battle_points >= needed ? 'ok' : 'bad'}">⚔️ ${fmt(town.battle_points)} / ${needed} πόντοι</span>
|
||
</div>`;
|
||
}
|
||
|
||
const reasonHtml = cel.reason
|
||
? `<div class="reason-box ${cel.status === 'cooldown' ? '' : 'error'}">${cel.reason}</div>`
|
||
: '';
|
||
|
||
const btnLabel = cel.status === 'cooldown' ? `⏰ ${cel.reason}`
|
||
: cel.status === 'unavailable' ? '✖ Μη διαθέσιμο'
|
||
: `▶ Εκκίνηση ${label}`;
|
||
|
||
return `
|
||
<div class="cel-card ${cel.status}">
|
||
<div class="cel-card-title">${label}</div>
|
||
${costsHtml}
|
||
${reasonHtml}
|
||
<button class="cel-btn primary"
|
||
${cel.available ? '' : 'disabled'}
|
||
onclick="openModal('${town.town_id}','${town.town_name}','${type}')">
|
||
${cel.available ? `▶ Εκκίνηση` : (cel.status === 'cooldown' ? `⏰ Σε αναμονή` : `✖ Μη διαθέσιμο`)}
|
||
</button>
|
||
</div>`;
|
||
}
|
||
|
||
function fmt(n) {
|
||
if (n >= 1000) return (n / 1000).toFixed(1) + 'k';
|
||
return n;
|
||
}
|
||
|
||
// ── Modal ────────────────────────────────────────────────────────
|
||
function openModal(townId, townName, celType) {
|
||
pendingCmd = { townId, townName, celType };
|
||
document.getElementById('modal-title').textContent = `Επιβεβαίωση — ${TYPE_LABELS[celType]}`;
|
||
|
||
let rows = `
|
||
<div class="modal-row"><span>Πόλη</span><span>${townName}</span></div>`;
|
||
|
||
if (celType === 'party') {
|
||
rows += `
|
||
<div class="modal-row"><span>Ξύλο</span><span>-15.000</span></div>
|
||
<div class="modal-row"><span>Πέτρα</span><span>-18.000</span></div>
|
||
<div class="modal-row"><span>Σίδερο</span><span>-15.000</span></div>`;
|
||
} else {
|
||
rows += `<div class="modal-row"><span>Πόντοι Μάχης</span><span>-300</span></div>`;
|
||
}
|
||
|
||
document.getElementById('modal-rows').innerHTML = rows;
|
||
document.getElementById('btn-confirm').disabled = false;
|
||
document.getElementById('confirm-modal').classList.add('open');
|
||
}
|
||
|
||
function closeModal() {
|
||
document.getElementById('confirm-modal').classList.remove('open');
|
||
pendingCmd = null;
|
||
}
|
||
|
||
async function fireCommand() {
|
||
if (!pendingCmd) return;
|
||
document.getElementById('btn-confirm').disabled = true;
|
||
|
||
// Capture before closeModal() nulls pendingCmd
|
||
const { townId, townName, celType } = pendingCmd;
|
||
try {
|
||
const r = await fetch('/dashboard/culture-command', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
player_id: PLAYER_ID,
|
||
world_id: WORLD_ID,
|
||
town_id: townId,
|
||
town_name: townName,
|
||
celebration_type: celType
|
||
})
|
||
});
|
||
const d = await r.json();
|
||
closeModal();
|
||
if (r.ok && d.ok) {
|
||
showToast(`✅ Εντολή στάλθηκε — ${TYPE_LABELS[celType]}`, 'ok');
|
||
fetchLog();
|
||
setTimeout(fetchAgora, 3000);
|
||
} else {
|
||
showToast(`❌ ${d.message || d.error || 'Αποτυχία'}`, 'err');
|
||
}
|
||
} catch (e) {
|
||
showToast(`❌ Σφάλμα: ${e}`, 'err');
|
||
}
|
||
}
|
||
|
||
// ── Log ──────────────────────────────────────────────────────────
|
||
function toggleLog() {
|
||
logOpen = !logOpen;
|
||
document.getElementById('log-body').classList.toggle('open', logOpen);
|
||
document.getElementById('log-toggle-icon').textContent = logOpen ? '▲' : '▼';
|
||
}
|
||
|
||
async function fetchLog() {
|
||
try {
|
||
const r = await fetch(`/dashboard/culture-log?player_id=${PLAYER_ID}&world_id=${WORLD_ID}`);
|
||
const rows = await r.json();
|
||
const body = document.getElementById('log-body');
|
||
const empty = document.getElementById('log-empty');
|
||
|
||
if (!rows.length) {
|
||
if (empty) empty.style.display = 'block';
|
||
return;
|
||
}
|
||
if (empty) empty.style.display = 'none';
|
||
|
||
const existing = new Set([...body.querySelectorAll('.log-entry')].map(el => el.dataset.id));
|
||
|
||
rows.forEach(row => {
|
||
if (existing.has(String(row.id))) return;
|
||
const el = document.createElement('div');
|
||
el.className = 'log-entry';
|
||
el.dataset.id = row.id;
|
||
|
||
const t = new Date(row.fired_at + 'Z');
|
||
const time = t.toLocaleTimeString('el-GR', { hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
||
|
||
const costStr = row.cost_battle_pts
|
||
? `-${row.cost_battle_pts} πόντοι`
|
||
: `-${fmt(row.cost_wood)} ξύλο / -${fmt(row.cost_stone)} πέτρα / -${fmt(row.cost_iron)} σίδερο`;
|
||
|
||
el.innerHTML = `
|
||
<span class="log-time">${time}</span>
|
||
<span class="log-town">${row.town_name}</span>
|
||
<span class="log-type">${TYPE_LABELS[row.celebration_type] || row.celebration_type}</span>
|
||
<span class="log-status ${row.status}">${row.status === 'success' ? '✅' : row.status === 'failed' ? '❌' : '⏳'}</span>
|
||
<span class="log-msg">${costStr}${row.result_msg ? ' — ' + row.result_msg : ''}</span>`;
|
||
body.insertBefore(el, body.firstChild);
|
||
});
|
||
} catch (e) { /* silent */ }
|
||
}
|
||
|
||
// ── Online status ────────────────────────────────────────────────
|
||
async function checkStatus() {
|
||
try {
|
||
const r = await fetch(`/dashboard/client-status?player_id=${PLAYER_ID}`);
|
||
const d = await r.json();
|
||
const dot = document.getElementById('status-dot');
|
||
const text = document.getElementById('status-text');
|
||
dot.className = 'status-dot' + (d.online ? ' online' : '');
|
||
text.textContent = d.online ? 'Online' : 'Offline';
|
||
} catch (e) { /* silent */ }
|
||
}
|
||
|
||
// ── Toast ────────────────────────────────────────────────────────
|
||
function showToast(msg, type) {
|
||
const t = document.getElementById('toast');
|
||
t.textContent = msg;
|
||
t.className = `toast ${type} show`;
|
||
setTimeout(() => { t.className = 'toast'; }, 3500);
|
||
}
|
||
|
||
// ── Search ───────────────────────────────────────────────────────
|
||
document.getElementById('search').addEventListener('input', renderTownList);
|
||
|
||
// ── Init ─────────────────────────────────────────────────────────
|
||
fetchAgora();
|
||
fetchLog();
|
||
checkStatus();
|
||
setInterval(fetchAgora, 30000);
|
||
setInterval(fetchLog, 15000);
|
||
setInterval(checkStatus, 30000);
|
||
</script>
|
||
</body>
|
||
</html>
|