568 lines
24 KiB
JavaScript
568 lines
24 KiB
JavaScript
/* ============================================================
|
||
public/js/buildings/gildenhalle.js
|
||
============================================================ */
|
||
|
||
let gh_initialized = false;
|
||
let gh_page = 1;
|
||
let gh_search = '';
|
||
let gh_playerGuild = null;
|
||
let gh_isGuildLeader = false;
|
||
|
||
function ghLoadCSS() {
|
||
if (!document.querySelector('link[href="/css/gildenhalle.css"]')) {
|
||
const l = document.createElement('link');
|
||
l.rel = 'stylesheet'; l.href = '/css/gildenhalle.css';
|
||
document.head.appendChild(l);
|
||
}
|
||
}
|
||
|
||
/* ── Popup sicherstellen ────────────────────────────────── */
|
||
function ghEnsurePopup() {
|
||
if (document.getElementById('gildenhalle-popup')) return;
|
||
|
||
const popup = document.createElement('div');
|
||
popup.id = 'gildenhalle-popup';
|
||
popup.className = 'qm-popup';
|
||
popup.innerHTML = `
|
||
<div class="qm-popup-header">
|
||
<span class="qm-popup-title">⚔ Gildenhalle</span>
|
||
<div class="baz-header-right">
|
||
<span class="qm-popup-close" id="gh-close">✕</span>
|
||
</div>
|
||
</div>
|
||
<div class="mp-body-wrap">
|
||
<aside class="mp-tabs" id="gh-tabs">
|
||
<button class="mp-tab mp-tab-active" data-tab="gh-panel-1">
|
||
<span class="mp-tab-dot"></span><span>Gilden</span>
|
||
</button>
|
||
<button class="mp-tab" data-tab="gh-panel-2">
|
||
<span class="mp-tab-dot"></span><span>Eigene Gilde</span>
|
||
</button>
|
||
<button class="mp-tab" data-tab="gh-panel-3">
|
||
<span class="mp-tab-dot"></span><span>Gilden Aufgaben</span>
|
||
</button>
|
||
</aside>
|
||
<div class="mp-content">
|
||
|
||
<!-- ── PANEL 1: Gilden suchen ───────────────────── -->
|
||
<div class="mp-panel active" id="gh-panel-1" style="overflow:hidden;">
|
||
<div class="mp-col-header">Gilden</div>
|
||
<div class="gh-search-bar">
|
||
<input class="gh-search-input" id="gh-search-input"
|
||
placeholder="Gildennamen oder Tag suchen…" />
|
||
<button class="gh-search-btn" id="gh-search-btn">Suchen</button>
|
||
<button class="gh-search-btn" id="gh-create-toggle-btn"
|
||
style="border-color:var(--guild-text);color:var(--guild-text);">+ Gründen</button>
|
||
</div>
|
||
<div class="gh-guild-grid" id="gh-guild-grid">
|
||
<div class="gh-loading">Lade Gilden…</div>
|
||
</div>
|
||
<div class="gh-pagination" id="gh-pagination"></div>
|
||
|
||
<!-- Gründen-Formular (versteckt) -->
|
||
<div id="gh-create-form-wrap" style="display:none;position:absolute;inset:0;
|
||
background:rgba(10,6,2,.96);z-index:20;overflow-y:auto;">
|
||
<div class="gh-create-form">
|
||
<div class="mp-col-header" style="margin-bottom:4px;">Neue Gilde gründen</div>
|
||
<div class="gh-form-row">
|
||
<label class="gh-form-label">Gildenname *</label>
|
||
<input class="gh-form-input" id="gh-new-name" maxlength="50" placeholder="Name der Gilde">
|
||
</div>
|
||
<div class="gh-form-row">
|
||
<label class="gh-form-label">Tag (2–6 Zeichen) *</label>
|
||
<input class="gh-form-input" id="gh-new-tag" maxlength="6" placeholder="z.B. DOK"
|
||
style="text-transform:uppercase;">
|
||
</div>
|
||
<div class="gh-form-row">
|
||
<label class="gh-form-label">Beschreibung</label>
|
||
<textarea class="gh-form-textarea" id="gh-new-desc" maxlength="255"
|
||
placeholder="Kurze Beschreibung…"></textarea>
|
||
</div>
|
||
<div class="gh-form-row">
|
||
<label class="gh-form-label">Beitrittsmodus</label>
|
||
<select class="gh-form-select" id="gh-new-open">
|
||
<option value="1">Offen – jeder kann beitreten</option>
|
||
<option value="0">Geschlossen – nur per Anfrage</option>
|
||
</select>
|
||
</div>
|
||
<div class="gh-error-msg" id="gh-create-error"></div>
|
||
<button class="gh-create-btn" id="gh-create-submit">⚔ GILDE GRÜNDEN</button>
|
||
<button class="gh-search-btn" id="gh-create-cancel"
|
||
style="text-align:center;">Abbrechen</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── PANEL 2: Eigene Gilde ─────────────────────── -->
|
||
<div class="mp-panel" id="gh-panel-2" style="overflow:hidden;flex-direction:column;">
|
||
<div class="mp-col-header">Eigene Gilde</div>
|
||
<div id="gh-my-content" style="flex:1;overflow-y:auto;display:flex;flex-direction:column;">
|
||
<div class="gh-loading">Lade…</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── PANEL 3: Gilden Aufgaben ───────────────────── -->
|
||
<div class="mp-panel" id="gh-panel-3" style="overflow:hidden;flex-direction:column;">
|
||
<div class="mp-col-header">Gilden Aufgaben</div>
|
||
<div id="gh-tasks-content" style="flex:1;overflow-y:auto;">
|
||
<div class="gh-loading">Lade Aufgaben…</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>`;
|
||
|
||
document.body.appendChild(popup);
|
||
|
||
popup.querySelector('#gh-close').addEventListener('click', ghClose);
|
||
|
||
/* Tab-Wechsel */
|
||
popup.querySelectorAll('.mp-tab').forEach(btn => {
|
||
btn.addEventListener('click', () => {
|
||
popup.querySelectorAll('.mp-tab').forEach(t => t.classList.remove('mp-tab-active'));
|
||
popup.querySelectorAll('.mp-panel').forEach(p => p.classList.remove('active'));
|
||
btn.classList.add('mp-tab-active');
|
||
const panel = document.getElementById(btn.dataset.tab);
|
||
panel?.classList.add('active');
|
||
|
||
if (btn.dataset.tab === 'gh-panel-2') ghLoadMyGuild();
|
||
if (btn.dataset.tab === 'gh-panel-3') ghLoadTasks();
|
||
});
|
||
});
|
||
|
||
/* Gründen-Toggle */
|
||
function ghUpdateCreateBtn() {
|
||
const btn = document.getElementById('gh-create-toggle-btn');
|
||
if (!btn) return;
|
||
if (gh_isGuildLeader) {
|
||
btn.disabled = true;
|
||
btn.title = 'Gildenmeister können keine weitere Gilde gründen';
|
||
btn.style.opacity = '0.4';
|
||
btn.style.cursor = 'not-allowed';
|
||
} else {
|
||
btn.disabled = false;
|
||
btn.title = '';
|
||
btn.style.opacity = '';
|
||
btn.style.cursor = '';
|
||
}
|
||
}
|
||
|
||
document.getElementById('gh-create-toggle-btn').addEventListener('click', () => {
|
||
if (gh_isGuildLeader) return;
|
||
document.getElementById('gh-create-form-wrap').style.display = 'block';
|
||
document.getElementById('gh-create-error').textContent = '';
|
||
});
|
||
document.getElementById('gh-create-cancel').addEventListener('click', () => {
|
||
document.getElementById('gh-create-form-wrap').style.display = 'none';
|
||
});
|
||
document.getElementById('gh-create-submit').addEventListener('click', ghCreateGuild);
|
||
|
||
/* Suche */
|
||
document.getElementById('gh-search-btn').addEventListener('click', () => {
|
||
gh_search = document.getElementById('gh-search-input').value.trim();
|
||
gh_page = 1;
|
||
ghLoadGuildList();
|
||
});
|
||
document.getElementById('gh-search-input').addEventListener('keydown', e => {
|
||
if (e.key === 'Enter') document.getElementById('gh-search-btn').click();
|
||
});
|
||
|
||
/* Drag */
|
||
const header = popup.querySelector('.qm-popup-header');
|
||
let dragging=false, sx,sy,sl,st;
|
||
header.style.cursor='grab';
|
||
header.addEventListener('mousedown', e => {
|
||
if (e.target.classList.contains('qm-popup-close')) return;
|
||
dragging=true; header.style.cursor='grabbing';
|
||
const r=popup.getBoundingClientRect();
|
||
sx=e.clientX; sy=e.clientY; sl=r.left; st=r.top;
|
||
popup.style.transform='none';
|
||
popup.style.left=sl+'px'; popup.style.top=st+'px';
|
||
e.preventDefault();
|
||
});
|
||
document.addEventListener('mousemove', e => {
|
||
if (!dragging) return;
|
||
popup.style.left=(sl+(e.clientX-sx))+'px';
|
||
popup.style.top=(st+(e.clientY-sy))+'px';
|
||
});
|
||
document.addEventListener('mouseup', () => { dragging=false; header.style.cursor='grab'; });
|
||
}
|
||
|
||
/* ════════════════════════════════════════════
|
||
PANEL 1: Gilden-Liste laden
|
||
════════════════════════════════════════════ */
|
||
async function ghLoadGuildList() {
|
||
const grid = document.getElementById('gh-guild-grid');
|
||
const pagination = document.getElementById('gh-pagination');
|
||
if (!grid) return;
|
||
grid.innerHTML = '<div class="gh-loading">Lade Gilden…</div>';
|
||
if (pagination) pagination.innerHTML = '';
|
||
|
||
try {
|
||
const res = await fetch(`/api/gildenhalle/list?search=${encodeURIComponent(gh_search)}&page=${gh_page}`);
|
||
const data = await res.json();
|
||
gh_playerGuild = data.playerGuildId;
|
||
gh_isGuildLeader = data.isGuildLeader || false;
|
||
ghUpdateCreateBtn();
|
||
|
||
if (!data.guilds.length) {
|
||
grid.innerHTML = '<div class="gh-empty">Keine Gilden gefunden.</div>';
|
||
return;
|
||
}
|
||
|
||
grid.innerHTML = data.guilds.map(g => `
|
||
<div class="gh-guild-card">
|
||
<div class="gh-guild-header">
|
||
<span class="gh-guild-tag">[${g.tag}]</span>
|
||
<span class="gh-guild-name">${g.name}</span>
|
||
</div>
|
||
<div class="gh-guild-desc">${g.description || '<em>Keine Beschreibung</em>'}</div>
|
||
<div class="gh-guild-meta">
|
||
<span>Lv. ${g.level} · ${g.member_count}/${g.max_members} Mitgl.</span>
|
||
<span class="${g.open ? 'gh-guild-open' : 'gh-guild-closed'}">
|
||
${g.open ? '✔ Offen' : '🔒 Anfrage'}
|
||
</span>
|
||
</div>
|
||
<button class="gh-join-btn ${g.has_pending_request ? 'pending' : ''}"
|
||
data-id="${g.id}" data-open="${g.open}"
|
||
${gh_playerGuild || g.has_pending_request ? 'disabled' : ''}>
|
||
${g.has_pending_request ? '⏳ Anfrage ausstehend'
|
||
: gh_playerGuild === g.id ? '✔ Meine Gilde'
|
||
: gh_playerGuild ? '(In Gilde)'
|
||
: g.open ? '⚔ Beitreten' : '✉ Anfrage stellen'}
|
||
</button>
|
||
</div>`).join('');
|
||
|
||
grid.querySelectorAll('.gh-join-btn:not([disabled])').forEach(btn => {
|
||
btn.addEventListener('click', () => ghJoinGuild(
|
||
parseInt(btn.dataset.id), btn.dataset.open === '1', btn
|
||
));
|
||
});
|
||
|
||
ghRenderPagination(pagination, data.totalPages, data.total);
|
||
} catch (err) {
|
||
grid.innerHTML = '<div class="gh-empty">Fehler beim Laden.</div>';
|
||
console.error('[GH]', err);
|
||
}
|
||
}
|
||
|
||
/* ── Gilde beitreten ────────────────────────────────────── */
|
||
async function ghJoinGuild(guildId, isOpen, btn) {
|
||
btn.disabled = true;
|
||
btn.textContent = '…';
|
||
|
||
let message = null;
|
||
if (!isOpen) {
|
||
message = window.prompt('Nachricht an die Gilde (optional):') ?? '';
|
||
}
|
||
|
||
try {
|
||
const res = await fetch(`/api/gildenhalle/join/${guildId}`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ message }),
|
||
});
|
||
const data = await res.json();
|
||
if (!res.ok) { alert(data.error || 'Fehler'); btn.disabled=false; btn.textContent=isOpen?'⚔ Beitreten':'✉ Anfrage stellen'; return; }
|
||
|
||
if (data.joined) {
|
||
ghShowToast('✔ Erfolgreich der Gilde beigetreten!');
|
||
gh_page = 1; ghLoadGuildList();
|
||
} else {
|
||
btn.classList.add('pending');
|
||
btn.textContent = '⏳ Anfrage ausstehend';
|
||
ghShowToast('✉ Beitrittsanfrage gesendet!');
|
||
}
|
||
} catch { btn.disabled=false; }
|
||
}
|
||
|
||
/* ── Gilde gründen ──────────────────────────────────────── */
|
||
async function ghCreateGuild() {
|
||
const name = document.getElementById('gh-new-name').value.trim();
|
||
const tag = document.getElementById('gh-new-tag').value.trim().toUpperCase();
|
||
const desc = document.getElementById('gh-new-desc').value.trim();
|
||
const open = document.getElementById('gh-new-open').value;
|
||
const errEl = document.getElementById('gh-create-error');
|
||
|
||
errEl.textContent = '';
|
||
if (!name || !tag) { errEl.textContent = 'Name und Tag sind Pflichtfelder.'; return; }
|
||
|
||
const btn = document.getElementById('gh-create-submit');
|
||
btn.disabled = true; btn.textContent = '…';
|
||
|
||
try {
|
||
const res = await fetch('/api/gildenhalle/create', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ name, tag, description: desc, open: open === '1' }),
|
||
});
|
||
const data = await res.json();
|
||
if (!res.ok) { errEl.textContent = data.error || 'Fehler'; btn.disabled=false; btn.textContent='⚔ GILDE GRÜNDEN'; return; }
|
||
|
||
document.getElementById('gh-create-form-wrap').style.display = 'none';
|
||
ghShowToast('🏰 Gilde erfolgreich gegründet!');
|
||
gh_page = 1; ghLoadGuildList();
|
||
|
||
// Wechsel zu "Eigene Gilde"
|
||
setTimeout(() => {
|
||
document.querySelector('[data-tab="gh-panel-2"]')?.click();
|
||
}, 500);
|
||
} catch { btn.disabled=false; btn.textContent='⚔ GILDE GRÜNDEN'; }
|
||
}
|
||
|
||
/* ════════════════════════════════════════════
|
||
PANEL 2: Eigene Gilde
|
||
════════════════════════════════════════════ */
|
||
async function ghLoadMyGuild() {
|
||
const container = document.getElementById('gh-my-content');
|
||
if (!container) return;
|
||
container.innerHTML = '<div class="gh-loading">Lade…</div>';
|
||
|
||
try {
|
||
const res = await fetch('/api/gildenhalle/my');
|
||
const data = await res.json();
|
||
|
||
if (!data.guild) {
|
||
container.innerHTML = `
|
||
<div class="gh-no-guild">
|
||
<div class="gh-no-guild-icon">🏰</div>
|
||
<div class="gh-no-guild-text">Du bist noch in keiner Gilde.<br>
|
||
Tritt einer Gilde bei oder gründe eine eigene.</div>
|
||
</div>`;
|
||
return;
|
||
}
|
||
|
||
const { guild, members, ranks, requests } = data;
|
||
const isLeader = (guild.leader_id === /* wird per Session gesetzt */ null);
|
||
|
||
// Mitglieder-Tabelle
|
||
const membersHtml = members.map(m => `
|
||
<tr>
|
||
<td>${m.name}</td>
|
||
<td>Lv. ${m.level}</td>
|
||
<td>
|
||
${guild.can_manage_ranks && m.id !== guild.leader_id
|
||
? `<select class="gh-rank-select" data-uid="${m.id}" onchange="ghChangeRank(${m.id},this.value)">
|
||
${ranks.map(r =>
|
||
`<option value="${r.id}" ${r.id === m.rank_id ? 'selected' : ''}>${r.name}</option>`
|
||
).join('')}
|
||
</select>`
|
||
: `<span class="gh-rank-badge ${m.id === guild.leader_id ? 'leader' : ''}">${m.rank_name || '–'}</span>`
|
||
}
|
||
</td>
|
||
<td style="font-size:10px;color:var(--guild-text)">
|
||
${new Date(m.joined_at).toLocaleDateString('de-DE')}
|
||
</td>
|
||
<td>
|
||
${guild.can_kick && m.id !== guild.leader_id
|
||
? `<button class="gh-kick-btn" onclick="ghKickMember(${m.id},'${m.name}')">Kick</button>`
|
||
: ''}
|
||
</td>
|
||
</tr>`).join('');
|
||
|
||
// Anfragen
|
||
const requestsHtml = requests.length > 0
|
||
? `<div class="gh-section-title">Beitrittsanfragen (${requests.length})</div>
|
||
<div class="gh-requests-list">
|
||
${requests.map(r => `
|
||
<div class="gh-request-row">
|
||
<span class="gh-request-name">${r.name}</span>
|
||
<span class="gh-request-msg">${r.message || '–'}</span>
|
||
${guild.can_invite
|
||
? `<button class="gh-req-accept" onclick="ghHandleRequest(${r.id},'accept')">✔</button>
|
||
<button class="gh-req-reject" onclick="ghHandleRequest(${r.id},'reject')">✕</button>`
|
||
: ''}
|
||
</div>`).join('')}
|
||
</div>`
|
||
: '';
|
||
|
||
container.innerHTML = `
|
||
<div class="gh-my-info">
|
||
<span class="gh-my-tag">[${guild.tag}]</span>
|
||
<div>
|
||
<div class="gh-my-name">${guild.name}</div>
|
||
<div class="gh-my-sub">Lv. ${guild.level} · ${members.length}/${guild.max_members} Mitglieder</div>
|
||
</div>
|
||
<button class="gh-leave-btn" onclick="ghLeaveGuild()">Austreten</button>
|
||
</div>
|
||
${requestsHtml}
|
||
<div class="gh-section-title">Mitglieder</div>
|
||
<div style="padding:0 4px;overflow-x:auto;">
|
||
<table class="gh-members-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Name</th><th>Level</th><th>Rang</th><th>Beigetreten</th><th></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>${membersHtml}</tbody>
|
||
</table>
|
||
</div>`;
|
||
} catch (err) {
|
||
container.innerHTML = '<div class="gh-empty">Fehler beim Laden.</div>';
|
||
console.error('[GH/my]', err);
|
||
}
|
||
}
|
||
|
||
/* ── Rang ändern ────────────────────────────────────────── */
|
||
window.ghChangeRank = async function(userId, rankId) {
|
||
try {
|
||
const res = await fetch(`/api/gildenhalle/member/${userId}/rank`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ rank_id: rankId }),
|
||
});
|
||
if (!res.ok) { const d=await res.json(); alert(d.error||'Fehler'); ghLoadMyGuild(); }
|
||
else ghShowToast('✔ Rang geändert');
|
||
} catch {}
|
||
};
|
||
|
||
/* ── Mitglied kicken ────────────────────────────────────── */
|
||
window.ghKickMember = async function(userId, name) {
|
||
if (!confirm(`${name} aus der Gilde entfernen?`)) return;
|
||
try {
|
||
const res = await fetch(`/api/gildenhalle/member/${userId}/kick`, { method: 'POST' });
|
||
const d = await res.json();
|
||
if (!res.ok) { alert(d.error||'Fehler'); return; }
|
||
ghShowToast(`✔ ${name} wurde entfernt`);
|
||
ghLoadMyGuild();
|
||
} catch {}
|
||
};
|
||
|
||
/* ── Anfrage annehmen/ablehnen ──────────────────────────── */
|
||
window.ghHandleRequest = async function(requestId, action) {
|
||
try {
|
||
const res = await fetch(`/api/gildenhalle/requests/${requestId}/${action}`, { method: 'POST' });
|
||
const d = await res.json();
|
||
if (!res.ok) { alert(d.error||'Fehler'); return; }
|
||
ghShowToast(action === 'accept' ? '✔ Anfrage angenommen' : '✕ Anfrage abgelehnt');
|
||
ghLoadMyGuild();
|
||
} catch {}
|
||
};
|
||
|
||
/* ── Gilde verlassen ────────────────────────────────────── */
|
||
window.ghLeaveGuild = async function() {
|
||
if (!confirm('Wirklich die Gilde verlassen?')) return;
|
||
try {
|
||
const res = await fetch('/api/gildenhalle/leave', { method: 'POST' });
|
||
const d = await res.json();
|
||
if (!res.ok) { alert(d.error||'Fehler'); return; }
|
||
ghShowToast('Gilde verlassen');
|
||
ghLoadMyGuild();
|
||
gh_page = 1; ghLoadGuildList();
|
||
} catch {}
|
||
};
|
||
|
||
/* ════════════════════════════════════════════
|
||
PANEL 3: Aufgaben
|
||
════════════════════════════════════════════ */
|
||
async function ghLoadTasks() {
|
||
const container = document.getElementById('gh-tasks-content');
|
||
if (!container) return;
|
||
container.innerHTML = '<div class="gh-loading">Lade Aufgaben…</div>';
|
||
|
||
try {
|
||
const res = await fetch('/api/gildenhalle/tasks');
|
||
if (res.status === 400) {
|
||
container.innerHTML = `
|
||
<div class="gh-no-guild">
|
||
<div class="gh-no-guild-icon">📋</div>
|
||
<div class="gh-no-guild-text">Tritt einer Gilde bei um gemeinsame Aufgaben zu sehen.</div>
|
||
</div>`;
|
||
return;
|
||
}
|
||
const data = await res.json();
|
||
|
||
if (!data.tasks?.length) {
|
||
container.innerHTML = '<div class="gh-empty">Keine Aufgaben für heute.</div>';
|
||
return;
|
||
}
|
||
|
||
const rewardIcons = { gold: '🪙', gems: '💎', wood: '🪵', iron: '⚙️' };
|
||
|
||
container.innerHTML = `
|
||
<div style="padding:10px 16px;font-family:'Cinzel',serif;font-size:11px;
|
||
color:var(--guild-text);border-bottom:1px solid rgba(139,106,42,.25);">
|
||
Gemeinsame Aufgaben der Gilde <strong style="color:#5a2a00;">${data.guildName}</strong>
|
||
</div>
|
||
<div class="gh-tasks-list">
|
||
${data.tasks.map(t => {
|
||
const pct = Math.min(100, Math.round((t.current_amount / t.target_amount) * 100));
|
||
const done = t.completed || t.current_amount >= t.target_amount;
|
||
const icon = rewardIcons[t.reward_type] || '🪙';
|
||
return `
|
||
<div class="gh-task-card ${done ? 'completed' : ''}">
|
||
<div class="gh-task-header">
|
||
<span class="gh-task-label">${t.label}</span>
|
||
${done
|
||
? `<span class="gh-task-done-badge">✔ Erledigt</span>`
|
||
: `<span class="gh-task-reward">${icon} ${t.reward_amount} ${t.reward_type}</span>`}
|
||
</div>
|
||
<div class="gh-task-progress-wrap">
|
||
<div class="gh-task-bar-track">
|
||
<div class="gh-task-bar-fill ${done?'completed':''}"
|
||
style="width:${pct}%"></div>
|
||
</div>
|
||
<div class="gh-task-progress-text">
|
||
<span>${t.current_amount} / ${t.target_amount}</span>
|
||
<span>${pct}% · Mein Beitrag: ${t.my_contribution || 0}</span>
|
||
</div>
|
||
</div>
|
||
</div>`;
|
||
}).join('')}
|
||
</div>`;
|
||
} catch (err) {
|
||
container.innerHTML = '<div class="gh-empty">Fehler beim Laden.</div>';
|
||
console.error('[GH/tasks]', err);
|
||
}
|
||
}
|
||
|
||
/* ── Pagination ─────────────────────────────────────────── */
|
||
function ghRenderPagination(el, totalPages, total) {
|
||
if (!el || !totalPages || totalPages <= 1) return;
|
||
el.innerHTML = `
|
||
<button class="gh-page-btn" id="gh-prev" ${gh_page===1?'disabled':''}>◀</button>
|
||
${Array.from({length:totalPages},(_,i)=>i+1).map(p =>
|
||
`<button class="gh-page-btn ${p===gh_page?'active':''}" data-page="${p}">${p}</button>`
|
||
).join('')}
|
||
<button class="gh-page-btn" id="gh-next" ${gh_page===totalPages?'disabled':''}>▶</button>`;
|
||
el.querySelector('#gh-prev')?.addEventListener('click', () => { if(gh_page>1){gh_page--;ghLoadGuildList();} });
|
||
el.querySelector('#gh-next')?.addEventListener('click', () => { if(gh_page<totalPages){gh_page++;ghLoadGuildList();} });
|
||
el.querySelectorAll('[data-page]').forEach(btn =>
|
||
btn.addEventListener('click', () => { gh_page=parseInt(btn.dataset.page); ghLoadGuildList(); })
|
||
);
|
||
}
|
||
|
||
/* ── Toast ──────────────────────────────────────────────── */
|
||
function ghShowToast(msg) {
|
||
const t = document.createElement('div');
|
||
t.className = 'baz-toast'; t.textContent = msg;
|
||
document.body.appendChild(t);
|
||
setTimeout(() => t.remove(), 2800);
|
||
}
|
||
|
||
/* ── Schließen ──────────────────────────────────────────── */
|
||
function ghClose() {
|
||
document.getElementById('gildenhalle-popup')?.classList.remove('active');
|
||
document.getElementById('qm-overlay')?.classList.remove('active');
|
||
}
|
||
|
||
/* ════════════════════════════════════════════
|
||
EXPORT
|
||
════════════════════════════════════════════ */
|
||
export function loadGildenhalle() {
|
||
ghLoadCSS();
|
||
ghEnsurePopup();
|
||
|
||
const popup = document.getElementById('gildenhalle-popup');
|
||
const overlay = document.getElementById('qm-overlay');
|
||
popup.style.left = '50%'; popup.style.top = '50%';
|
||
popup.style.transform = 'translate(-50%,-50%) scale(1)';
|
||
popup.classList.add('active');
|
||
overlay?.classList.add('active');
|
||
|
||
if (!gh_initialized) {
|
||
gh_initialized = true;
|
||
ghLoadGuildList();
|
||
}
|
||
}
|