Weiter an den Locations gearbeitet
Some checks failed
Host-Based Deploy (Java 21 Fix) / build-and-run (push) Has been cancelled
Some checks failed
Host-Based Deploy (Java 21 Fix) / build-and-run (push) Has been cancelled
This commit is contained in:
@@ -12,7 +12,7 @@
|
||||
.back-link:hover { color:var(--color-primary); }
|
||||
|
||||
.loc-header { display:flex; gap:1rem; align-items:flex-start; margin-bottom:1.25rem; flex-wrap:wrap; }
|
||||
.loc-avatar { width:96px; height:96px; border-radius:12px; background:var(--color-secondary); object-fit:cover; flex-shrink:0; display:flex; align-items:center; justify-content:center; font-size:2.5rem; overflow:hidden; border:2px solid var(--color-secondary); }
|
||||
.loc-avatar { width:120px; height:120px; border-radius:12px; background:var(--color-secondary); object-fit:cover; flex-shrink:0; display:flex; align-items:center; justify-content:center; font-size:2.5rem; overflow:hidden; border:2px solid var(--color-secondary); }
|
||||
.loc-avatar img { width:100%; height:100%; object-fit:cover; }
|
||||
.loc-meta { flex:1; min-width:0; }
|
||||
.loc-name { font-size:1.4rem; font-weight:700; margin:0 0 0.3rem; }
|
||||
@@ -26,7 +26,7 @@
|
||||
.hours-table td:first-child { font-weight:500; width:100px; }
|
||||
.hours-closed { color:var(--color-muted); }
|
||||
|
||||
.gallery-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(120px,1fr)); gap:0.6rem; }
|
||||
.gallery-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(180px,1fr)); gap:0.6rem; }
|
||||
.gallery-img-wrap { position:relative; aspect-ratio:1; border-radius:8px; overflow:hidden; background:var(--color-secondary); }
|
||||
.gallery-img-wrap img { width:100%; height:100%; object-fit:cover; cursor:pointer; transition:opacity 0.15s; }
|
||||
.gallery-img-wrap img:hover { opacity:0.88; }
|
||||
@@ -59,19 +59,72 @@
|
||||
/* Lightbox */
|
||||
.lb { display:none; position:fixed; inset:0; background:rgba(0,0,0,.9); z-index:300; align-items:center; justify-content:center; }
|
||||
.lb.open { display:flex; }
|
||||
.lb img { max-width:95vw; max-height:95vh; border-radius:8px; object-fit:contain; }
|
||||
.lb img { max-width:85vw; max-height:90vh; border-radius:8px; object-fit:contain; }
|
||||
.lb-close { position:absolute; top:1rem; right:1rem; background:none; border:none; color:#fff; font-size:1.5rem; cursor:pointer; }
|
||||
.lb-nav { position:absolute; top:50%; transform:translateY(-50%); background:rgba(255,255,255,.15); border:none; color:#fff; font-size:2.5rem; line-height:1; cursor:pointer; padding:0.3rem 0.8rem; border-radius:8px; transition:background 0.15s; user-select:none; }
|
||||
.lb-nav:hover { background:rgba(255,255,255,.3); }
|
||||
.lb-nav:disabled { opacity:0.2; cursor:default; }
|
||||
.lb-prev { left:1rem; }
|
||||
.lb-next { right:1rem; }
|
||||
|
||||
.owner-badge { display:inline-flex; align-items:center; gap:0.3rem; font-size:0.75rem; background:var(--color-secondary); border-radius:4px; padding:0.2rem 0.5rem; color:var(--color-muted); margin-top:0.3rem; }
|
||||
.owner-actions { display:flex; gap:0.5rem; flex-wrap:wrap; margin-top:0.75rem; }
|
||||
|
||||
/* Tabs */
|
||||
.tab-bar { display:flex; gap:0; border-bottom:2px solid var(--color-secondary); margin-bottom:1.25rem; flex-wrap:wrap; }
|
||||
.tab-btn { background:none; border:none; border-bottom:2px solid transparent; color:var(--color-muted); cursor:pointer; font-size:0.88rem; font-weight:600; padding:0.65rem 1rem; margin-bottom:-2px; border-radius:0; width:auto; margin-top:0; transition:color 0.15s, border-color 0.15s; white-space:nowrap; }
|
||||
.tab-btn:hover { color:var(--color-text); background:none; }
|
||||
.tab-btn.active { color:var(--color-text); border-bottom-color:var(--color-primary); }
|
||||
.tab-panel { display:none; }
|
||||
.tab-panel.active { display:block; }
|
||||
|
||||
/* Posteingang */
|
||||
.inbox-list { display:flex; flex-direction:column; gap:0.5rem; }
|
||||
.inbox-item { display:flex; align-items:center; gap:0.65rem; padding:0.65rem 0.75rem; border-radius:8px; background:var(--color-secondary); cursor:pointer; transition:opacity 0.15s; }
|
||||
.inbox-item:hover { opacity:0.85; }
|
||||
.inbox-avatar { width:36px; height:36px; border-radius:50%; background:var(--color-card); display:flex; align-items:center; justify-content:center; font-size:0.9rem; flex-shrink:0; overflow:hidden; }
|
||||
.inbox-avatar img { width:100%; height:100%; object-fit:cover; }
|
||||
.inbox-info { flex:1; min-width:0; }
|
||||
.inbox-name { font-weight:600; font-size:0.88rem; }
|
||||
.inbox-preview { font-size:0.78rem; color:var(--color-muted); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
||||
.inbox-unread { background:var(--color-primary); color:#fff; font-size:0.65rem; font-weight:700; border-radius:9999px; padding:0.1rem 0.35rem; flex-shrink:0; }
|
||||
|
||||
/* Posteingang Chat */
|
||||
.inbox-chat { display:none; flex-direction:column; gap:0; margin-top:0.75rem; border:1px solid var(--color-secondary); border-radius:10px; overflow:hidden; }
|
||||
.inbox-chat.open { display:flex; }
|
||||
.inbox-chat-header { display:flex; align-items:center; gap:0.5rem; padding:0.65rem 0.9rem; background:var(--color-secondary); font-weight:600; font-size:0.88rem; }
|
||||
.inbox-chat-back { background:none; border:none; color:var(--color-muted); cursor:pointer; font-size:1.1rem; padding:0; margin:0; width:auto; line-height:1; }
|
||||
.inbox-chat-close { background:none; border:none; color:var(--color-muted); cursor:pointer; font-size:1rem; padding:0.15rem 0.35rem; margin:0 0 0 auto; width:auto; line-height:1; border-radius:4px; transition:background 0.15s, color 0.15s; }
|
||||
.inbox-chat-close:hover { background:var(--color-card); color:var(--color-text); }
|
||||
.inbox-chat-messages { max-height:320px; overflow-y:auto; padding:0.75rem 1rem; display:flex; flex-direction:column; gap:0.4rem; }
|
||||
.inbox-bubble-wrap { display:flex; flex-direction:column; }
|
||||
.inbox-bubble-wrap.me { align-items:flex-end; }
|
||||
.inbox-bubble-wrap.them { align-items:flex-start; }
|
||||
.inbox-bubble { max-width:75%; padding:0.45rem 0.8rem; border-radius:12px; font-size:0.88rem; line-height:1.4; word-break:break-word; }
|
||||
.inbox-bubble-wrap.me .inbox-bubble { background:var(--color-primary); color:#fff; border-bottom-right-radius:4px; }
|
||||
.inbox-bubble-wrap.them .inbox-bubble { background:var(--color-secondary); color:var(--color-text); border-bottom-left-radius:4px; }
|
||||
.inbox-bubble-time { font-size:0.68rem; color:var(--color-muted); margin-top:0.1rem; padding:0 0.2rem; }
|
||||
.inbox-reply-area { display:flex; gap:0.5rem; padding:0.6rem 0.9rem; border-top:1px solid var(--color-secondary); align-items:center; }
|
||||
.inbox-reply-area input { flex:1; }
|
||||
.inbox-reply-btn { width:auto; margin-top:0; padding:0.5rem 1rem; flex-shrink:0; }
|
||||
.inbox-lock-hint { font-size:0.8rem; color:var(--color-muted); padding:0.5rem 0.9rem; border-top:1px solid var(--color-secondary); background:var(--color-secondary); }
|
||||
.inbox-reply-trigger { padding:0.6rem 0.9rem; border-top:1px solid var(--color-secondary); }
|
||||
.inbox-reply-trigger .btn { font-size:0.85rem; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="main">
|
||||
<a href="/community/locations.html" class="back-link">← Locations</a>
|
||||
<body class="app">
|
||||
|
||||
<div id="content">
|
||||
<p style="color:var(--color-muted);">Wird geladen…</p>
|
||||
<!-- ── Admin-Autocomplete (außerhalb .main, damit overflow-y:auto nicht clippt) ── -->
|
||||
<ul id="adminSearchList" style="position:fixed;display:none;background:var(--color-card);border:1px solid var(--color-secondary);border-radius:6px;z-index:400;list-style:none;margin:0;padding:0;max-height:200px;overflow-y:auto;box-shadow:0 4px 12px rgba(0,0,0,.15);"></ul>
|
||||
<ul id="ownerSearchList" style="position:fixed;display:none;background:var(--color-card);border:1px solid var(--color-secondary);border-radius:6px;z-index:400;list-style:none;margin:0;padding:0;max-height:200px;overflow-y:auto;box-shadow:0 4px 12px rgba(0,0,0,.15);"></ul>
|
||||
|
||||
<div class="main">
|
||||
<div class="content">
|
||||
<a href="/community/locations.html" class="back-link">← Locations</a>
|
||||
|
||||
<div id="content">
|
||||
<p style="color:var(--color-muted);">Wird geladen…</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -131,7 +184,7 @@
|
||||
<label>Titel *</label>
|
||||
<input type="text" id="eventTitle" maxlength="200">
|
||||
<label>Beschreibung <span style="color:var(--color-muted);font-size:0.8rem;">(max. 1000 Zeichen)</span></label>
|
||||
<textarea id="eventDesc" maxlength="1000" rows="4" style="resize:vertical;"></textarea>
|
||||
<textarea id="eventDesc" maxlength="1000" rows="4" style="resize:vertical;width:100%;box-sizing:border-box;padding:0.65rem 0.9rem;border:1px solid var(--color-secondary);border-radius:6px;background:var(--color-secondary);color:var(--color-text);font-size:1rem;outline:none;font-family:inherit;transition:border-color 0.2s;" onfocus="this.style.borderColor='var(--color-primary)'" onblur="this.style.borderColor='var(--color-secondary)'"></textarea>
|
||||
<label>Datum & Uhrzeit *</label>
|
||||
<input type="datetime-local" id="eventStartAt">
|
||||
<div class="modal-footer">
|
||||
@@ -141,20 +194,45 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Hinweis-Modal ──────────────────────────────────────────────────────── -->
|
||||
<div class="modal-overlay" id="alertModal">
|
||||
<div class="modal" style="width:min(380px,95vw);">
|
||||
<p id="alertMessage" style="margin:0 0 1.25rem;font-size:0.95rem;"></p>
|
||||
<div class="modal-footer">
|
||||
<button class="btn" onclick="document.getElementById('alertModal').classList.remove('open')">OK</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Bestätigungs-Modal ─────────────────────────────────────────────────── -->
|
||||
<div class="modal-overlay" id="confirmModal">
|
||||
<div class="modal" style="width:min(380px,95vw);">
|
||||
<h3 id="confirmTitle">Bestätigung</h3>
|
||||
<p id="confirmMessage" style="color:var(--color-muted);font-size:0.92rem;margin:0 0 0.25rem;"></p>
|
||||
<div class="modal-footer">
|
||||
<button class="btn" style="background:var(--color-secondary);color:var(--color-text);" onclick="closeConfirm()">Abbrechen</button>
|
||||
<button class="btn" id="confirmOkBtn" style="background:#c0392b;">Löschen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Galerie Lightbox ───────────────────────────────────────────────────── -->
|
||||
<div class="lb" id="lightbox" onclick="closeLightbox()">
|
||||
<button class="lb-close" onclick="closeLightbox()">✕</button>
|
||||
<img id="lbImg" src="" alt="">
|
||||
<button class="lb-nav lb-prev" id="lbPrev" onclick="event.stopPropagation();lbNav(-1)">‹</button>
|
||||
<img id="lbImg" src="" alt="" onclick="event.stopPropagation()">
|
||||
<button class="lb-nav lb-next" id="lbNext" onclick="event.stopPropagation();lbNav(1)">›</button>
|
||||
</div>
|
||||
|
||||
<script src="/js/sidebar.js"></script>
|
||||
<script src="/js/icons.js"></script>
|
||||
<script src="/js/sidebar.js"></script>
|
||||
<script>
|
||||
const params = new URLSearchParams(location.search);
|
||||
const locationId = params.get('id');
|
||||
let locDetail = null;
|
||||
let myUserId = null;
|
||||
let isOwner = false;
|
||||
let isAdmin = false;
|
||||
let isFollowing = false;
|
||||
|
||||
// ── Bild-Resize ───────────────────────────────────────────────────────────────
|
||||
@@ -192,6 +270,18 @@ function formatDate(dt) {
|
||||
+ ', ' + d.toLocaleTimeString('de-DE', { hour:'2-digit', minute:'2-digit' }) + ' Uhr';
|
||||
}
|
||||
|
||||
// ── Tabs ──────────────────────────────────────────────────────────────────────
|
||||
const VALID_TABS = ['grunddaten', 'admins', 'posteingang', 'veranstaltungen'];
|
||||
|
||||
function switchTab(name) {
|
||||
if (!VALID_TABS.includes(name)) name = 'grunddaten';
|
||||
document.querySelectorAll('.tab-btn').forEach(btn =>
|
||||
btn.classList.toggle('active', btn.dataset.tab === name));
|
||||
document.querySelectorAll('.tab-panel').forEach(panel =>
|
||||
panel.classList.toggle('active', panel.id === 'tab-' + name));
|
||||
history.replaceState(null, '', location.pathname + location.search + '#' + name);
|
||||
}
|
||||
|
||||
// ── Lade Seite ────────────────────────────────────────────────────────────────
|
||||
async function loadPage() {
|
||||
if (!locationId) { document.getElementById('content').innerHTML = '<p>Keine Location-ID angegeben.</p>'; return; }
|
||||
@@ -207,12 +297,24 @@ async function loadPage() {
|
||||
myUserId = me.userId;
|
||||
}
|
||||
|
||||
locDetail = await locRes.json();
|
||||
isOwner = locDetail.ownerId === myUserId;
|
||||
locDetail = await locRes.json();
|
||||
isOwner = locDetail.ownerId === myUserId;
|
||||
isAdmin = isOwner || !!locDetail.isAdmin;
|
||||
isFollowing = !!locDetail.following;
|
||||
|
||||
renderPage();
|
||||
loadEvents();
|
||||
|
||||
if (isAdmin) {
|
||||
const chatWithId = new URLSearchParams(location.search).get('chatWith');
|
||||
const hash = location.hash.replace('#', '');
|
||||
const initialTab = chatWithId ? 'posteingang' : (VALID_TABS.includes(hash) ? hash : 'grunddaten');
|
||||
switchTab(initialTab);
|
||||
renderAdminList();
|
||||
loadInbox();
|
||||
loadEvents();
|
||||
} else {
|
||||
loadEvents();
|
||||
}
|
||||
}
|
||||
|
||||
function renderPage() {
|
||||
@@ -230,6 +332,7 @@ function renderPage() {
|
||||
<button class="btn" id="followBtn" style="font-size:0.85rem;${isFollowing ? 'background:var(--color-primary);color:#fff;' : 'background:var(--color-secondary);color:var(--color-text);'}" onclick="toggleFollow()">
|
||||
${isFollowing ? '★ Abonniert' : '☆ Abonnieren'}
|
||||
</button>
|
||||
${loc.virtualUserId && myUserId && !isAdmin ? `<button class="btn" style="font-size:0.85rem;" onclick="contactLocation('${loc.virtualUserId}')">✉ Kontaktieren</button>` : ''}
|
||||
</div>`;
|
||||
|
||||
let hoursHtml = '';
|
||||
@@ -248,7 +351,7 @@ function renderPage() {
|
||||
|
||||
const galleryHtml = buildGalleryHtml(loc.gallery || []);
|
||||
|
||||
document.getElementById('content').innerHTML = `
|
||||
const locHeaderHtml = `
|
||||
<div class="loc-header">
|
||||
<div class="loc-avatar">${imgHtml}</div>
|
||||
<div class="loc-meta">
|
||||
@@ -257,10 +360,9 @@ function renderPage() {
|
||||
${loc.description ? `<div class="loc-desc">${escHtml(loc.description)}</div>` : ''}
|
||||
${ownerActions}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${hoursHtml}
|
||||
</div>`;
|
||||
|
||||
const gallerySection = `
|
||||
<div class="section-title">
|
||||
Galerie
|
||||
${isOwner ? `<label class="btn" style="font-size:0.8rem;cursor:pointer;">
|
||||
@@ -268,14 +370,87 @@ function renderPage() {
|
||||
<input type="file" accept="image/*" style="display:none;" onchange="uploadGalleryImage(this)">
|
||||
</label>` : ''}
|
||||
</div>
|
||||
<div class="gallery-grid" id="galleryGrid">${galleryHtml}</div>
|
||||
<div class="gallery-grid" id="galleryGrid">${galleryHtml}</div>`;
|
||||
|
||||
const eventsSection = `
|
||||
<div class="section-title">
|
||||
Veranstaltungen
|
||||
${isOwner ? `<button class="btn" style="font-size:0.8rem;" onclick="openEventModal()">+ Veranstaltung erstellen</button>` : ''}
|
||||
</div>
|
||||
<div class="event-list" id="eventList"><p style="color:var(--color-muted);font-size:0.9rem;">Wird geladen…</p></div>
|
||||
`;
|
||||
<div id="pastEventsSection" style="display:none;">
|
||||
<div class="section-title" style="margin-top:1.5rem;">Vergangene Veranstaltungen</div>
|
||||
<div class="event-list" id="pastEventList"></div>
|
||||
</div>`;
|
||||
|
||||
if (isAdmin) {
|
||||
document.getElementById('content').innerHTML = `
|
||||
<div class="tab-bar">
|
||||
<button class="tab-btn" data-tab="grunddaten" onclick="switchTab('grunddaten')">Grunddaten</button>
|
||||
<button class="tab-btn" data-tab="admins" onclick="switchTab('admins')">Administrator*Innen</button>
|
||||
<button class="tab-btn" data-tab="posteingang" onclick="switchTab('posteingang')">Posteingang</button>
|
||||
<button class="tab-btn" data-tab="veranstaltungen" onclick="switchTab('veranstaltungen')">Veranstaltungen</button>
|
||||
</div>
|
||||
|
||||
<div class="tab-panel" id="tab-grunddaten">
|
||||
${locHeaderHtml}
|
||||
${hoursHtml}
|
||||
${gallerySection}
|
||||
</div>
|
||||
|
||||
<div class="tab-panel" id="tab-admins">
|
||||
<div id="adminList" style="display:flex;flex-direction:column;gap:0.5rem;margin-bottom:0.75rem;"></div>
|
||||
<div style="display:flex;gap:0.5rem;align-items:center;flex-wrap:wrap;">
|
||||
<div style="flex:1;min-width:180px;">
|
||||
<input type="text" id="adminSearchInput" placeholder="Mitglied suchen…" autocomplete="off"
|
||||
oninput="onAdminSearch()" style="width:100%;box-sizing:border-box;">
|
||||
</div>
|
||||
<button class="btn" style="font-size:0.85rem;white-space:nowrap;" onclick="addAdminFromSearch()">+ Admin hinzufügen</button>
|
||||
</div>
|
||||
${isOwner ? `
|
||||
<div style="margin-top:1rem;padding-top:1rem;border-top:1px solid var(--color-secondary);">
|
||||
<div style="font-size:0.82rem;color:var(--color-muted);margin-bottom:0.5rem;">Inhaberrechte übertragen</div>
|
||||
<div style="display:flex;gap:0.5rem;align-items:center;flex-wrap:wrap;">
|
||||
<div style="flex:1;min-width:180px;">
|
||||
<input type="text" id="ownerSearchInput" placeholder="Mitglied suchen…" autocomplete="off"
|
||||
oninput="onOwnerSearch()" style="width:100%;box-sizing:border-box;">
|
||||
</div>
|
||||
<button class="btn" style="font-size:0.85rem;white-space:nowrap;background:#c0392b;" onclick="transferOwner()">Inhaberwechsel</button>
|
||||
</div>
|
||||
</div>` : ''}
|
||||
</div>
|
||||
|
||||
<div class="tab-panel" id="tab-posteingang">
|
||||
<div id="inboxList" class="inbox-list"><p style="color:var(--color-muted);font-size:0.9rem;">Wird geladen…</p></div>
|
||||
<div class="inbox-chat" id="inboxChat">
|
||||
<div class="inbox-chat-header">
|
||||
<button class="inbox-chat-back" onclick="closeInboxChat()" aria-label="Zurück">‹</button>
|
||||
<span id="inboxChatName" style="flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;"></span>
|
||||
<button class="inbox-chat-close" onclick="closeInboxChat()" aria-label="Schließen">✕</button>
|
||||
</div>
|
||||
<div class="inbox-chat-messages" id="inboxChatMessages"></div>
|
||||
<div class="inbox-lock-hint" id="inboxLockHint" style="display:none;"></div>
|
||||
<div class="inbox-reply-trigger" id="inboxReplyTrigger" style="display:none;">
|
||||
<button class="btn" onclick="startReplying()">✎ Antworten</button>
|
||||
</div>
|
||||
<div class="inbox-reply-area" id="inboxReplyArea" style="display:none;">
|
||||
<input type="text" id="inboxReplyInput" placeholder="Antwort eingeben…" autocomplete="off"
|
||||
onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();sendInboxReply();}">
|
||||
<button class="btn inbox-reply-btn" onclick="sendInboxReply()">↑</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-panel" id="tab-veranstaltungen">
|
||||
${eventsSection}
|
||||
</div>`;
|
||||
} else {
|
||||
document.getElementById('content').innerHTML = `
|
||||
${locHeaderHtml}
|
||||
${hoursHtml}
|
||||
${gallerySection}
|
||||
${eventsSection}`;
|
||||
}
|
||||
}
|
||||
|
||||
function buildGalleryHtml(gallery) {
|
||||
@@ -298,7 +473,7 @@ async function uploadGalleryImage(input) {
|
||||
headers: {'Content-Type':'application/json'},
|
||||
body: JSON.stringify({ imageData })
|
||||
});
|
||||
if (res.status === 422) { alert('Maximal 20 Galeriebilder erlaubt.'); return; }
|
||||
if (res.status === 422) { showAlert('Maximal 20 Galeriebilder erlaubt.'); return; }
|
||||
if (!res.ok) throw new Error();
|
||||
const img = await res.json();
|
||||
const grid = document.getElementById('galleryGrid');
|
||||
@@ -307,18 +482,34 @@ async function uploadGalleryImage(input) {
|
||||
<img src="data:image/jpeg;base64,${img.imageData}" alt="Galeriebild" onclick="openLightbox(this.src)">
|
||||
<button class="gallery-del-btn" onclick="deleteGalleryImage('${img.imageId}')">✕</button>
|
||||
</div>`);
|
||||
} catch { alert('Fehler beim Hochladen.'); }
|
||||
} catch { showAlert('Fehler beim Hochladen.'); }
|
||||
input.value = '';
|
||||
}
|
||||
|
||||
async function deleteGalleryImage(imageId) {
|
||||
if (!confirm('Bild löschen?')) return;
|
||||
const res = await fetch(`/locations/${locationId}/gallery/${imageId}`, { method: 'DELETE' });
|
||||
if (!res.ok) { alert('Fehler beim Löschen.'); return; }
|
||||
if (!res.ok) { showAlert('Fehler beim Löschen.'); return; }
|
||||
await loadPage();
|
||||
}
|
||||
|
||||
// ── Events ─────────────────────────────────────────────────────────────────────
|
||||
function buildEventCard(e, isPast) {
|
||||
const imgHtml = e.imageData
|
||||
? `<img src="data:image/jpeg;base64,${e.imageData}" alt="${escHtml(e.title)}">`
|
||||
: '🗓';
|
||||
const opacity = isPast ? 'opacity:0.6;' : '';
|
||||
return `
|
||||
<a class="event-card" href="/community/event-detail.html?id=${e.eventId}" style="${opacity}">
|
||||
<div class="event-card-img">${imgHtml}</div>
|
||||
<div class="event-card-body">
|
||||
<div class="event-card-title">${escHtml(e.title)}</div>
|
||||
<div class="event-card-date">${formatDate(e.startAt)}</div>
|
||||
<div class="event-card-attendees">${e.attendeeCount} Teilnehmer*in(nen)${e.attendingMe ? ' · Du nimmst teil' : ''}</div>
|
||||
</div>
|
||||
</a>`;
|
||||
}
|
||||
|
||||
async function loadEvents() {
|
||||
const res = await fetch(`/locations/${locationId}/events`);
|
||||
if (!res.ok) return;
|
||||
@@ -326,37 +517,50 @@ async function loadEvents() {
|
||||
const list = document.getElementById('eventList');
|
||||
if (!list) return;
|
||||
|
||||
if (events.length === 0) {
|
||||
list.innerHTML = '<p style="color:var(--color-muted);font-size:0.9rem;">Noch keine Veranstaltungen.</p>';
|
||||
return;
|
||||
}
|
||||
const now = new Date();
|
||||
const future = events.filter(e => new Date(e.startAt) >= now);
|
||||
const past = events.filter(e => new Date(e.startAt) < now)
|
||||
.slice(-5) // letzte 5
|
||||
.reverse(); // neueste zuerst
|
||||
|
||||
list.innerHTML = events.map(e => {
|
||||
const imgHtml = e.imageData
|
||||
? `<img src="data:image/jpeg;base64,${e.imageData}" alt="${escHtml(e.title)}">`
|
||||
: '🗓';
|
||||
const deleteBtn = isOwner
|
||||
? `<button class="btn" style="font-size:0.75rem;margin-top:0.3rem;background:var(--color-secondary);color:var(--color-text);padding:0.2rem 0.5rem;" onclick="event.preventDefault();deleteEvent('${e.eventId}')">Löschen</button>`
|
||||
: '';
|
||||
return `
|
||||
<a class="event-card" href="/community/event-detail.html?id=${e.eventId}">
|
||||
<div class="event-card-img">${imgHtml}</div>
|
||||
<div class="event-card-body">
|
||||
<div class="event-card-title">${escHtml(e.title)}</div>
|
||||
<div class="event-card-date">${formatDate(e.startAt)}</div>
|
||||
<div class="event-card-attendees">${e.attendeeCount} Teilnehmer*in(nen)${e.attendingMe ? ' · Du nimmst teil' : ''}</div>
|
||||
${deleteBtn}
|
||||
</div>
|
||||
</a>`;
|
||||
}).join('');
|
||||
list.innerHTML = future.length
|
||||
? future.map(e => buildEventCard(e, false)).join('')
|
||||
: '<p style="color:var(--color-muted);font-size:0.9rem;">Keine bevorstehenden Veranstaltungen.</p>';
|
||||
|
||||
const pastSection = document.getElementById('pastEventsSection');
|
||||
if (past.length && pastSection) {
|
||||
document.getElementById('pastEventList').innerHTML = past.map(e => buildEventCard(e, true)).join('');
|
||||
pastSection.style.display = '';
|
||||
} else if (pastSection) {
|
||||
pastSection.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// ── Lightbox ───────────────────────────────────────────────────────────────────
|
||||
let lbSrcs = [], lbIdx = 0;
|
||||
|
||||
function openLightbox(src) {
|
||||
document.getElementById('lbImg').src = src;
|
||||
lbSrcs = Array.from(document.querySelectorAll('#galleryGrid .gallery-img-wrap img')).map(i => i.src);
|
||||
lbIdx = Math.max(0, lbSrcs.indexOf(src));
|
||||
lbShow();
|
||||
document.getElementById('lightbox').classList.add('open');
|
||||
}
|
||||
function lbShow() {
|
||||
document.getElementById('lbImg').src = lbSrcs[lbIdx];
|
||||
document.getElementById('lbPrev').disabled = lbIdx === 0;
|
||||
document.getElementById('lbNext').disabled = lbIdx === lbSrcs.length - 1;
|
||||
}
|
||||
function lbNav(dir) {
|
||||
lbIdx = Math.max(0, Math.min(lbSrcs.length - 1, lbIdx + dir));
|
||||
lbShow();
|
||||
}
|
||||
function closeLightbox() { document.getElementById('lightbox').classList.remove('open'); }
|
||||
document.addEventListener('keydown', e => {
|
||||
if (!document.getElementById('lightbox').classList.contains('open')) return;
|
||||
if (e.key === 'Escape') closeLightbox();
|
||||
else if (e.key === 'ArrowLeft') lbNav(-1);
|
||||
else if (e.key === 'ArrowRight') lbNav(1);
|
||||
});
|
||||
|
||||
// ── Edit Modal ─────────────────────────────────────────────────────────────────
|
||||
let _editLq = null, _editHq = null, _editLat = null, _editLon = null, _editStreet = null, _editCity = null, _editCityTimer = null;
|
||||
@@ -489,7 +693,7 @@ document.addEventListener('click', e => {
|
||||
|
||||
async function submitEdit() {
|
||||
const name = document.getElementById('editName').value.trim();
|
||||
if (!name) { alert('Name darf nicht leer sein.'); return; }
|
||||
if (!name) { showAlert('Name darf nicht leer sein.'); return; }
|
||||
const addrVal = document.getElementById('editCity').value.trim();
|
||||
if (addrVal && _editLat == null) {
|
||||
document.getElementById('editLocMsg').textContent = 'Bitte eine Adresse aus der Vorschlagsliste auswählen oder per GPS ermitteln.';
|
||||
@@ -530,7 +734,7 @@ async function submitEdit() {
|
||||
closeEditModal();
|
||||
renderPage();
|
||||
loadEvents();
|
||||
} catch { alert('Fehler beim Speichern.'); }
|
||||
} catch { showAlert('Fehler beim Speichern.'); }
|
||||
finally { btn.disabled = false; btn.textContent = 'Speichern'; }
|
||||
}
|
||||
|
||||
@@ -547,14 +751,14 @@ async function toggleFollow() {
|
||||
btn.style.background = isFollowing ? 'var(--color-primary)' : 'var(--color-secondary)';
|
||||
btn.style.color = isFollowing ? '#fff' : 'var(--color-text)';
|
||||
}
|
||||
} catch (_) { alert('Fehler beim Aktualisieren des Abonnements.'); }
|
||||
} catch (_) { showAlert('Fehler beim Aktualisieren des Abonnements.'); }
|
||||
finally { if (btn) btn.disabled = false; }
|
||||
}
|
||||
|
||||
async function deleteLocation() {
|
||||
if (!confirm('Location wirklich löschen? Alle Veranstaltungen und Galeriebilder werden ebenfalls gelöscht.')) return;
|
||||
const res = await fetch(`/locations/${locationId}`, { method: 'DELETE' });
|
||||
if (!res.ok) { alert('Fehler beim Löschen.'); return; }
|
||||
if (!res.ok) { showAlert('Fehler beim Löschen.'); return; }
|
||||
window.location.href = '/community/locations.html';
|
||||
}
|
||||
|
||||
@@ -568,6 +772,9 @@ function openEventModal(evtId) {
|
||||
document.getElementById('eventTitle').value = '';
|
||||
document.getElementById('eventDesc').value = '';
|
||||
document.getElementById('eventStartAt').value = '';
|
||||
// Nur Termine in der Zukunft erlauben
|
||||
const nowLocal = new Date(Date.now() - new Date().getTimezoneOffset() * 60000).toISOString().slice(0, 16);
|
||||
document.getElementById('eventStartAt').min = nowLocal;
|
||||
document.getElementById('eventPicPreview').innerHTML = '🗓';
|
||||
document.getElementById('eventModal').classList.add('open');
|
||||
}
|
||||
@@ -582,8 +789,9 @@ async function onEventPicChange(input) {
|
||||
async function submitEvent() {
|
||||
const title = document.getElementById('eventTitle').value.trim();
|
||||
const startAt = document.getElementById('eventStartAt').value;
|
||||
if (!title) { alert('Bitte gib einen Titel ein.'); return; }
|
||||
if (!startAt) { alert('Bitte wähle Datum und Uhrzeit.'); return; }
|
||||
if (!title) { showAlert('Bitte gib einen Titel ein.'); return; }
|
||||
if (!startAt) { showAlert('Bitte wähle Datum und Uhrzeit.'); return; }
|
||||
if (new Date(startAt) <= new Date()) { showAlert('Der Termin muss in der Zukunft liegen.'); return; }
|
||||
|
||||
const btn = document.getElementById('eventSubmitBtn');
|
||||
btn.disabled = true; btn.textContent = 'Wird gespeichert…';
|
||||
@@ -610,15 +818,321 @@ async function submitEvent() {
|
||||
|
||||
closeEventModal();
|
||||
loadEvents();
|
||||
} catch { alert('Fehler beim Speichern.'); }
|
||||
} catch { showAlert('Fehler beim Speichern.'); }
|
||||
finally { btn.disabled = false; btn.textContent = 'Speichern'; }
|
||||
}
|
||||
|
||||
async function deleteEvent(eventId) {
|
||||
if (!confirm('Veranstaltung löschen?')) return;
|
||||
const res = await fetch(`/locations/${locationId}/events/${eventId}`, { method: 'DELETE' });
|
||||
if (!res.ok) { alert('Fehler beim Löschen.'); return; }
|
||||
loadEvents();
|
||||
function showAlert(message) {
|
||||
document.getElementById('alertMessage').textContent = message;
|
||||
document.getElementById('alertModal').classList.add('open');
|
||||
}
|
||||
|
||||
function openConfirm(title, message, onOk) {
|
||||
document.getElementById('confirmTitle').textContent = title;
|
||||
document.getElementById('confirmMessage').textContent = message;
|
||||
const btn = document.getElementById('confirmOkBtn');
|
||||
btn.onclick = () => { closeConfirm(); onOk(); };
|
||||
document.getElementById('confirmModal').classList.add('open');
|
||||
}
|
||||
function closeConfirm() { document.getElementById('confirmModal').classList.remove('open'); }
|
||||
|
||||
// ── Admin-Verwaltung ──────────────────────────────────────────────────────────
|
||||
let _adminSearchSelected = null;
|
||||
let _ownerSearchSelected = null;
|
||||
let _adminSearchTimer = null;
|
||||
let _ownerSearchTimer = null;
|
||||
|
||||
function renderAdminList() {
|
||||
const list = document.getElementById('adminList');
|
||||
if (!list || !locDetail.admins) return;
|
||||
list.innerHTML = locDetail.admins.map(a => {
|
||||
const pic = a.profilePicture
|
||||
? `<img src="data:image/jpeg;base64,${a.profilePicture}" alt="" style="width:100%;height:100%;object-fit:cover;border-radius:50%;">`
|
||||
: '◉';
|
||||
const badge = a.isOwner
|
||||
? `<span style="font-size:0.7rem;background:var(--color-primary);color:#fff;border-radius:4px;padding:0.1rem 0.4rem;margin-left:0.4rem;">Inhaber</span>`
|
||||
: '';
|
||||
const removeBtn = isAdmin && !a.isOwner
|
||||
? `<button onclick="removeAdmin('${a.userId}')" style="margin-left:auto;background:none;border:none;color:var(--color-muted);cursor:pointer;font-size:1rem;padding:0.2rem 0.4rem;" title="Entfernen">✕</button>`
|
||||
: '';
|
||||
return `<div style="display:flex;align-items:center;gap:0.6rem;padding:0.4rem 0;">
|
||||
<div style="width:32px;height:32px;border-radius:50%;background:var(--color-secondary);display:flex;align-items:center;justify-content:center;font-size:1rem;overflow:hidden;flex-shrink:0;">${pic}</div>
|
||||
<span style="font-size:0.9rem;">${escHtml(a.name)}</span>${badge}${removeBtn}
|
||||
</div>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
async function removeAdmin(userId) {
|
||||
const res = await fetch(`/locations/${locationId}/admins/${userId}`, { method: 'DELETE' });
|
||||
if (!res.ok) { showAlert('Fehler beim Entfernen.'); return; }
|
||||
locDetail.admins = locDetail.admins.filter(a => a.userId !== userId);
|
||||
renderAdminList();
|
||||
}
|
||||
|
||||
function onAdminSearch() {
|
||||
const q = document.getElementById('adminSearchInput').value.trim();
|
||||
_adminSearchSelected = null;
|
||||
clearTimeout(_adminSearchTimer);
|
||||
if (q.length < 2) { document.getElementById('adminSearchList').style.display = 'none'; return; }
|
||||
_adminSearchTimer = setTimeout(() => fetchUserSuggestions(q, 'adminSearchList', sel => { _adminSearchSelected = sel; }), 250);
|
||||
}
|
||||
|
||||
function onOwnerSearch() {
|
||||
const q = document.getElementById('ownerSearchInput').value.trim();
|
||||
_ownerSearchSelected = null;
|
||||
clearTimeout(_ownerSearchTimer);
|
||||
if (q.length < 2) { document.getElementById('ownerSearchList').style.display = 'none'; return; }
|
||||
_ownerSearchTimer = setTimeout(() => fetchUserSuggestions(q, 'ownerSearchList', sel => { _ownerSearchSelected = sel; }), 250);
|
||||
}
|
||||
|
||||
const _userCache = {};
|
||||
|
||||
async function fetchUserSuggestions(q, listId, onSelect) {
|
||||
try {
|
||||
const res = await fetch(`/social/users/search?q=${encodeURIComponent(q)}`);
|
||||
if (!res.ok) return;
|
||||
const users = await res.json();
|
||||
const ul = document.getElementById(listId);
|
||||
if (!users.length) { ul.style.display = 'none'; return; }
|
||||
users.forEach(u => { _userCache[u.userId] = u; });
|
||||
ul.innerHTML = users.map(u => `
|
||||
<li style="padding:0.45rem 0.75rem;cursor:pointer;font-size:0.9rem;border-bottom:1px solid var(--color-secondary);"
|
||||
onmousedown="event.preventDefault();selectUserSuggestion('${listId}','${u.userId}')">
|
||||
${escHtml(u.name)}
|
||||
</li>`).join('');
|
||||
ul.__selectFn = onSelect;
|
||||
// Dropdown unter dem zugehörigen Input positionieren
|
||||
const inputId = listId === 'adminSearchList' ? 'adminSearchInput' : 'ownerSearchInput';
|
||||
const rect = document.getElementById(inputId).getBoundingClientRect();
|
||||
ul.style.top = (rect.bottom + 2) + 'px';
|
||||
ul.style.left = rect.left + 'px';
|
||||
ul.style.width = rect.width + 'px';
|
||||
ul.style.display = '';
|
||||
} catch(_) {}
|
||||
}
|
||||
|
||||
function selectUserSuggestion(listId, userId) {
|
||||
const ul = document.getElementById(listId);
|
||||
const inputId = listId === 'adminSearchList' ? 'adminSearchInput' : 'ownerSearchInput';
|
||||
const user = _userCache[userId];
|
||||
if (!user) return;
|
||||
document.getElementById(inputId).value = user.name;
|
||||
ul.style.display = 'none';
|
||||
if (ul.__selectFn) ul.__selectFn(user);
|
||||
}
|
||||
|
||||
document.addEventListener('click', e => {
|
||||
['adminSearchList','ownerSearchList'].forEach(id => {
|
||||
const ul = document.getElementById(id);
|
||||
if (ul && !e.target.closest('#' + id) && e.target.id !== (id === 'adminSearchList' ? 'adminSearchInput' : 'ownerSearchInput'))
|
||||
ul.style.display = 'none';
|
||||
});
|
||||
});
|
||||
|
||||
async function addAdminFromSearch() {
|
||||
if (!_adminSearchSelected) { showAlert('Bitte wähle ein Mitglied aus der Liste.'); return; }
|
||||
const res = await fetch(`/locations/${locationId}/admins`, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type':'application/json'},
|
||||
body: JSON.stringify({ userId: _adminSearchSelected.userId })
|
||||
});
|
||||
if (res.status === 409) { showAlert('Diese Person ist bereits Admin.'); return; }
|
||||
if (!res.ok) { showAlert('Fehler beim Hinzufügen.'); return; }
|
||||
const added = await res.json();
|
||||
locDetail.admins = [...(locDetail.admins || []), added];
|
||||
renderAdminList();
|
||||
document.getElementById('adminSearchInput').value = '';
|
||||
_adminSearchSelected = null;
|
||||
}
|
||||
|
||||
async function transferOwner() {
|
||||
if (!_ownerSearchSelected) { showAlert('Bitte wähle ein Mitglied aus der Liste.'); return; }
|
||||
openConfirm(
|
||||
'Inhaberwechsel',
|
||||
`Inhaberrechte wirklich an „${_ownerSearchSelected.name}" übertragen? Diese Aktion kann nicht rückgängig gemacht werden.`,
|
||||
async () => {
|
||||
const res = await fetch(`/locations/${locationId}/admins/transfer-owner`, {
|
||||
method: 'PUT',
|
||||
headers: {'Content-Type':'application/json'},
|
||||
body: JSON.stringify({ userId: _ownerSearchSelected.userId })
|
||||
});
|
||||
if (!res.ok) { showAlert('Fehler beim Inhaberwechsel.'); return; }
|
||||
// Seite neu laden – aktueller User ist jetzt kein Inhaber mehr
|
||||
await loadPage();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// ── Kontaktieren ──────────────────────────────────────────────────────────────
|
||||
function contactLocation(virtualUserId) {
|
||||
window.location.href = '/community/nachrichten.html?partnerId=' + virtualUserId;
|
||||
}
|
||||
|
||||
// ── Posteingang (Admin) ───────────────────────────────────────────────────────
|
||||
let _inboxPartnerId = null;
|
||||
|
||||
async function loadInbox() {
|
||||
const el = document.getElementById('inboxList');
|
||||
if (!el) return;
|
||||
try {
|
||||
const res = await fetch(`/locations/${locationId}/inbox`);
|
||||
if (!res.ok) { el.innerHTML = '<p style="color:var(--color-muted);font-size:0.9rem;">Nicht verfügbar.</p>'; return; }
|
||||
const items = await res.json();
|
||||
if (items.length === 0) {
|
||||
el.innerHTML = '<p style="color:var(--color-muted);font-size:0.9rem;">Noch keine Nachrichten.</p>';
|
||||
return;
|
||||
}
|
||||
el.innerHTML = items.map(item => {
|
||||
const av = item.senderPicture
|
||||
? `<img src="data:image/png;base64,${item.senderPicture}" alt="">`
|
||||
: '◉';
|
||||
const unreadHtml = item.unreadCount > 0
|
||||
? `<span class="inbox-unread">${item.unreadCount}</span>` : '';
|
||||
return `<div class="inbox-item" onclick="openInboxChat('${item.senderId}','${escHtml(item.senderName)}')">
|
||||
<div class="inbox-avatar">${av}</div>
|
||||
<div class="inbox-info">
|
||||
<div class="inbox-name">${escHtml(item.senderName)}</div>
|
||||
<div class="inbox-preview">${escHtml(item.lastMessage)}</div>
|
||||
</div>
|
||||
${unreadHtml}
|
||||
</div>`;
|
||||
}).join('');
|
||||
|
||||
// URL-Parameter: Chat direkt öffnen (z.B. nach Klick auf Benachrichtigung)
|
||||
const chatWithId = new URLSearchParams(location.search).get('chatWith');
|
||||
if (chatWithId && !_inboxPartnerId) {
|
||||
const match = items.find(i => i.senderId === chatWithId);
|
||||
if (match) openInboxChat(match.senderId, match.senderName);
|
||||
}
|
||||
} catch { el.innerHTML = '<p style="color:var(--color-muted);font-size:0.9rem;">Fehler beim Laden.</p>'; }
|
||||
}
|
||||
|
||||
async function openInboxChat(partnerId, partnerName) {
|
||||
_inboxPartnerId = partnerId;
|
||||
document.getElementById('inboxChatName').textContent = partnerName;
|
||||
document.getElementById('inboxChatMessages').innerHTML = '<p style="color:var(--color-muted);font-size:0.85rem;text-align:center;">Wird geladen…</p>';
|
||||
document.getElementById('inboxChat').classList.add('open');
|
||||
document.getElementById('inboxReplyInput').value = '';
|
||||
// Eingabebereich zurücksetzen
|
||||
document.getElementById('inboxReplyTrigger').style.display = 'none';
|
||||
document.getElementById('inboxReplyArea').style.display = 'none';
|
||||
document.getElementById('inboxLockHint').style.display = 'none';
|
||||
|
||||
const virtualId = locDetail.virtualUserId;
|
||||
try {
|
||||
const res = await fetch(`/locations/${locationId}/inbox/${partnerId}`);
|
||||
if (!res.ok) throw new Error();
|
||||
const data = await res.json();
|
||||
const messages = data.messages || [];
|
||||
|
||||
const container = document.getElementById('inboxChatMessages');
|
||||
container.innerHTML = '';
|
||||
if (messages.length === 0) {
|
||||
container.innerHTML = '<p style="color:var(--color-muted);font-size:0.85rem;text-align:center;">Noch keine Nachrichten.</p>';
|
||||
} else {
|
||||
messages.forEach(m => {
|
||||
const isLocationSender = m.senderId === virtualId;
|
||||
const wrap = document.createElement('div');
|
||||
wrap.className = 'inbox-bubble-wrap ' + (isLocationSender ? 'me' : 'them');
|
||||
const time = new Date(m.sentAt).toLocaleTimeString('de', { hour: '2-digit', minute: '2-digit' });
|
||||
wrap.innerHTML = `<div class="inbox-bubble">${escHtml(m.text)}</div><div class="inbox-bubble-time">${time}</div>`;
|
||||
container.appendChild(wrap);
|
||||
});
|
||||
}
|
||||
requestAnimationFrame(() => { container.scrollTop = container.scrollHeight; });
|
||||
|
||||
// Lock-Status anzeigen
|
||||
applyLockUi(data);
|
||||
loadInbox();
|
||||
} catch {
|
||||
document.getElementById('inboxChatMessages').innerHTML = '<p style="color:var(--color-muted);font-size:0.85rem;text-align:center;">Fehler beim Laden.</p>';
|
||||
}
|
||||
}
|
||||
|
||||
function applyLockUi(data) {
|
||||
const trigger = document.getElementById('inboxReplyTrigger');
|
||||
const area = document.getElementById('inboxReplyArea');
|
||||
const lockHint = document.getElementById('inboxLockHint');
|
||||
|
||||
if (data.lockedByMe) {
|
||||
// Ich habe die Sperre → Eingabefeld direkt zeigen
|
||||
trigger.style.display = 'none';
|
||||
area.style.display = '';
|
||||
lockHint.style.display = 'none';
|
||||
document.getElementById('inboxReplyInput').focus();
|
||||
} else if (data.canReply) {
|
||||
// Frei → "Antworten"-Button zeigen
|
||||
trigger.style.display = '';
|
||||
area.style.display = 'none';
|
||||
lockHint.style.display = 'none';
|
||||
} else {
|
||||
// Gesperrt durch anderen Admin
|
||||
trigger.style.display = 'none';
|
||||
area.style.display = 'none';
|
||||
lockHint.style.display = '';
|
||||
lockHint.textContent = `Wird gerade von ${data.lockedByName || 'einem anderen Admin'} beantwortet.`;
|
||||
}
|
||||
}
|
||||
|
||||
async function startReplying() {
|
||||
if (!_inboxPartnerId) return;
|
||||
try {
|
||||
const res = await fetch(`/locations/${locationId}/inbox/${_inboxPartnerId}/lock`, {
|
||||
method: 'POST'
|
||||
});
|
||||
if (res.status === 409) {
|
||||
const body = await res.json();
|
||||
const name = body.lockedByName || 'einem anderen Admin';
|
||||
document.getElementById('inboxReplyTrigger').style.display = 'none';
|
||||
document.getElementById('inboxLockHint').style.display = '';
|
||||
document.getElementById('inboxLockHint').textContent =
|
||||
`Wird gerade von ${name} beantwortet.`;
|
||||
return;
|
||||
}
|
||||
if (!res.ok) { showAlert('Sperre konnte nicht erworben werden.'); return; }
|
||||
document.getElementById('inboxReplyTrigger').style.display = 'none';
|
||||
document.getElementById('inboxReplyArea').style.display = '';
|
||||
document.getElementById('inboxReplyInput').focus();
|
||||
} catch { showAlert('Fehler beim Sperren.'); }
|
||||
}
|
||||
|
||||
function closeInboxChat() {
|
||||
_inboxPartnerId = null;
|
||||
document.getElementById('inboxChat').classList.remove('open');
|
||||
document.getElementById('inboxReplyTrigger').style.display = 'none';
|
||||
document.getElementById('inboxReplyArea').style.display = 'none';
|
||||
document.getElementById('inboxLockHint').style.display = 'none';
|
||||
loadInbox();
|
||||
}
|
||||
|
||||
async function sendInboxReply() {
|
||||
if (!_inboxPartnerId) return;
|
||||
const input = document.getElementById('inboxReplyInput');
|
||||
const text = input.value.trim();
|
||||
if (!text) return;
|
||||
input.value = '';
|
||||
try {
|
||||
const res = await fetch(`/locations/${locationId}/inbox/${_inboxPartnerId}/reply`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ text })
|
||||
});
|
||||
if (res.status === 409) {
|
||||
const body = await res.json().catch(() => ({}));
|
||||
const name = body.lockedByName || 'einem anderen Admin';
|
||||
input.value = text;
|
||||
// Eingabe sperren, Hinweis zeigen
|
||||
document.getElementById('inboxReplyArea').style.display = 'none';
|
||||
document.getElementById('inboxLockHint').style.display = '';
|
||||
document.getElementById('inboxLockHint').textContent =
|
||||
`Wird gerade von ${name} beantwortet. Deine Nachricht wurde nicht gesendet.`;
|
||||
return;
|
||||
}
|
||||
if (!res.ok) { showAlert('Fehler beim Senden.'); input.value = text; return; }
|
||||
// Konversation neu laden (Lock ist nach Senden bei mir)
|
||||
const partnerName = document.getElementById('inboxChatName').textContent;
|
||||
await openInboxChat(_inboxPartnerId, partnerName);
|
||||
} catch { showAlert('Fehler beim Senden.'); input.value = text; }
|
||||
}
|
||||
|
||||
// ── Init ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user