Auto-Commit: 2026-07-05 10:51:13
This commit is contained in:
@@ -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, "&")
|
||||
if (!str) return '';
|
||||
return String(str).replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user