Auto-Commit: 2026-07-05 10:51:13

This commit is contained in:
2026-07-05 10:51:13 +02:00
parent 7ff01ea431
commit ed9f0d058c
9 changed files with 410 additions and 88 deletions
+153 -80
View File
@@ -93,55 +93,32 @@ document.addEventListener('DOMContentLoaded', () => {
const feedbackList = document.getElementById('feedback-list');
const filterButtons = document.querySelectorAll('.filter-btn');
// Default Seed Data
const defaultFeedback = [
{
id: 'fb-1',
author: 'Markus T.',
type: 'feature',
title: 'QR-Code Download für Gäste',
text: 'Es wäre super cool, wenn nach der Aufnahme ein QR-Code angezeigt wird, über den sich die Gäste das Foto direkt auf das Handy laden können.',
upvotes: 42,
voted: false
},
{
id: 'fb-2',
author: 'Sabrina',
type: 'love',
title: 'Sticker-Editor ist genial!',
text: 'Unsere Hochzeitsgäste haben stundenlang Emojis und Eventtexte verschoben. Die Bedienung ist wirklich super intuitiv und einfach.',
upvotes: 28,
voted: false
},
{
id: 'fb-3',
author: 'EventTech GmbH',
type: 'bug',
title: 'Drucker-Timeout bei schlechtem WLAN',
text: 'Manchmal meldet die App einen Fehler, wenn der Drucker im lokalen Netzwerk kurz die Verbindung verliert. Ein automatischer Retry wäre hilfreich.',
upvotes: 15,
voted: false
}
];
// Load from LocalStorage or seed defaults
let feedbackData = JSON.parse(localStorage.getItem('schnappix_feedback'));
if (!feedbackData) {
feedbackData = defaultFeedback;
localStorage.setItem('schnappix_feedback', JSON.stringify(feedbackData));
}
let feedbackData = [];
// Upvote storage track (local to session/device)
let upvotedIds = JSON.parse(localStorage.getItem('schnappix_upvoted_ids')) || [];
const saveToLocalStorage = () => {
localStorage.setItem('schnappix_feedback', JSON.stringify(feedbackData));
const saveUpvotesToLocal = () => {
localStorage.setItem('schnappix_upvoted_ids', JSON.stringify(upvotedIds));
};
// Admin State
let isAdmin = sessionStorage.getItem('schnappix_is_admin') === 'true';
const loadFeedback = async () => {
if (!feedbackList) return;
try {
const response = await fetch('feedback_backend.php?action=get');
const result = await response.json();
if (result.success) {
feedbackData = result.data;
renderFeedback(getActiveFilter());
}
} catch (error) {
console.error('Error loading feedback:', error);
}
};
const renderFeedback = (filter = 'all') => {
if (!feedbackList) return;
feedbackList.innerHTML = '';
@@ -166,20 +143,37 @@ document.addEventListener('DOMContentLoaded', () => {
categoryLabel = 'Lob';
}
// Show delete button if user is admin
const deleteBtnHtml = isAdmin
? `<button class="fb-admin-delete-btn" data-id="${item.id}" title="Eintrag löschen"><i class="fa-solid fa-trash"></i> Löschen</button>`
: '';
// Show admin actions if user is admin
let adminActionsHtml = '';
if (isAdmin) {
adminActionsHtml = `
<div class="fb-admin-actions" style="margin-top: 10px; margin-bottom: 15px; display: flex; gap: 10px;">
<button class="fb-admin-answer-btn btn btn-secondary" style="padding: 5px 10px; font-size: 12px;" data-id="${item.id}" title="Antworten"><i class="fa-solid fa-reply"></i> Antworten</button>
<button class="fb-admin-delete-btn btn" style="padding: 5px 10px; font-size: 12px; background: rgba(255,50,50,0.2); color: #ff6b6b;" data-id="${item.id}" title="Eintrag löschen"><i class="fa-solid fa-trash"></i> Löschen</button>
</div>
`;
}
let answerHtml = '';
if (item.answer) {
answerHtml = `
<div class="fb-answer" style="margin-top: 15px; padding: 12px; background: rgba(var(--accent-color-rgb), 0.1); border-left: 3px solid var(--accent-color); border-radius: 4px;">
<div class="fb-answer-header" style="font-weight: bold; font-size: 13px; color: var(--accent-color); margin-bottom: 5px;"><i class="fa-solid fa-check-circle"></i> Offizielle Antwort</div>
<p style="margin: 0; font-size: 14px;">${escapeHtml(item.answer)}</p>
</div>
`;
}
card.innerHTML = `
<div class="fb-card-content">
<div class="fb-meta">
<span class="fb-tag ${item.type}">${badgeIcon} ${categoryLabel}</span>
<span class="fb-author">von ${escapeHtml(item.author)}</span>
${deleteBtnHtml}
</div>
${adminActionsHtml}
<h4>${escapeHtml(item.title)}</h4>
<p>${escapeHtml(item.text)}</p>
${answerHtml}
</div>
<div class="fb-upvote-section">
<button class="upvote-btn ${isVoted ? 'voted' : ''}" data-id="${item.id}" aria-label="Abstimmen">
@@ -194,25 +188,74 @@ document.addEventListener('DOMContentLoaded', () => {
setupUpvoteButtons();
setupAdminDeleteButtons();
setupAdminAnswerButtons();
};
const setupAdminDeleteButtons = () => {
if (!isAdmin) return;
const deleteButtons = document.querySelectorAll('.fb-admin-delete-btn');
deleteButtons.forEach(btn => {
btn.addEventListener('click', () => {
btn.addEventListener('click', async () => {
const id = btn.getAttribute('data-id');
if (confirm('Möchten Sie diesen Eintrag wirklich permanent löschen?')) {
feedbackData = feedbackData.filter(item => item.id !== id);
saveToLocalStorage();
renderFeedback(getActiveFilter());
try {
const response = await fetch('feedback_backend.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'delete', id: id, secret: '1234' })
});
const result = await response.json();
if (result.success) {
loadFeedback();
} else {
alert('Fehler beim Löschen.');
}
} catch (e) {
console.error(e);
}
}
});
});
};
const setupAdminAnswerButtons = () => {
if (!isAdmin) return;
const answerButtons = document.querySelectorAll('.fb-admin-answer-btn');
answerButtons.forEach(btn => {
btn.addEventListener('click', () => {
const id = btn.getAttribute('data-id');
const item = feedbackData.find(i => i.id === id);
const currentAnswer = item ? (item.answer || '') : '';
const answer = prompt('Antwort für das Feedback (leer lassen zum Löschen der Antwort):', currentAnswer);
if (answer !== null) {
submitAnswer(id, answer);
}
});
});
};
const submitAnswer = async (id, answer) => {
try {
const response = await fetch('feedback_backend.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'answer', id: id, answer: answer, secret: '1234' })
});
const result = await response.json();
if (result.success) {
loadFeedback();
} else {
alert('Fehler beim Speichern der Antwort.');
}
} catch (e) {
console.error(e);
}
};
const escapeHtml = (str) => {
return str.replace(/&/g, "&amp;")
if (!str) return '';
return String(str).replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
@@ -222,22 +265,33 @@ document.addEventListener('DOMContentLoaded', () => {
const setupUpvoteButtons = () => {
const buttons = document.querySelectorAll('.upvote-btn');
buttons.forEach(btn => {
btn.addEventListener('click', () => {
btn.addEventListener('click', async () => {
const id = btn.getAttribute('data-id');
const index = feedbackData.findIndex(item => item.id === id);
const isVoted = upvotedIds.includes(id);
if (index !== -1) {
if (upvotedIds.includes(id)) {
// Undo upvote
feedbackData[index].upvotes--;
upvotedIds = upvotedIds.filter(vId => vId !== id);
} else {
// Upvote
feedbackData[index].upvotes++;
upvotedIds.push(id);
}
saveToLocalStorage();
renderFeedback(getActiveFilter());
// Optimistic UI update
const btnSpan = btn.querySelector('span');
let currentVotes = parseInt(btnSpan.textContent);
if (isVoted) {
btn.classList.remove('voted');
btnSpan.textContent = currentVotes - 1;
upvotedIds = upvotedIds.filter(vId => vId !== id);
} else {
btn.classList.add('voted');
btnSpan.textContent = currentVotes + 1;
upvotedIds.push(id);
}
saveUpvotesToLocal();
try {
await fetch('feedback_backend.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'upvote', id: id, undo: isVoted })
});
} catch (e) {
console.error('Error upvoting', e);
}
});
});
@@ -259,36 +313,53 @@ document.addEventListener('DOMContentLoaded', () => {
// Form Submission
if (feedbackForm) {
feedbackForm.addEventListener('submit', (e) => {
feedbackForm.addEventListener('submit', async (e) => {
e.preventDefault();
const authorInput = document.getElementById('fb-author');
const typeSelect = document.getElementById('fb-type');
const titleInput = document.getElementById('fb-title');
const textInput = document.getElementById('fb-text');
const submitBtn = feedbackForm.querySelector('button[type="submit"]');
const newFb = {
id: 'fb-' + Date.now(),
action: 'add',
author: authorInput.value,
type: typeSelect.value,
title: titleInput.value,
text: textInput.value,
upvotes: 1,
voted: true
text: textInput.value
};
feedbackData.push(newFb);
upvotedIds.push(newFb.id); // Auto-upvote own post
saveToLocalStorage();
// Reset form
feedbackForm.reset();
submitBtn.disabled = true;
submitBtn.textContent = 'Wird gesendet...';
// Re-render
renderFeedback(getActiveFilter());
// Smooth scroll to list
feedbackList.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
try {
const response = await fetch('feedback_backend.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newFb)
});
const result = await response.json();
if (result.success) {
// Auto-upvote own post
upvotedIds.push(result.data.id);
saveUpvotesToLocal();
feedbackForm.reset();
await loadFeedback(); // Reload the list
feedbackList.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
} else {
alert('Fehler beim Senden des Feedbacks.');
}
} catch (error) {
console.error(error);
alert('Netzwerkfehler. Bitte später erneut versuchen.');
} finally {
submitBtn.disabled = false;
submitBtn.textContent = 'Absenden';
}
});
}
@@ -421,5 +492,7 @@ document.addEventListener('DOMContentLoaded', () => {
});
// Initial render
renderFeedback();
if (feedbackList) {
loadFeedback();
}
});