Files
website/templates/social/notifications.html

381 lines
15 KiB
HTML

{% extends "base.html" %}
{% block title %}Benachrichtigungen - SysTades{% endblock %}
{% block content %}
<div class="container mx-auto px-4 py-8">
<!-- Header -->
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6 mb-6">
<div class="flex items-center justify-between">
<h1 class="text-3xl font-bold text-gray-900 dark:text-white">🔔 Benachrichtigungen</h1>
<button id="mark-all-read" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors">
Alle als gelesen markieren
</button>
</div>
</div>
<!-- Filter Tabs -->
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg mb-6">
<div class="border-b border-gray-200 dark:border-gray-600">
<nav class="flex space-x-8 px-6">
<button class="filter-btn active py-4 px-2 border-b-2 border-blue-500 font-medium text-blue-600 dark:text-blue-400" data-filter="all">
📥 Alle
</button>
<button class="filter-btn py-4 px-2 border-b-2 border-transparent font-medium text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200" data-filter="unread">
🔴 Ungelesen
</button>
<button class="filter-btn py-4 px-2 border-b-2 border-transparent font-medium text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200" data-filter="likes">
❤️ Likes
</button>
<button class="filter-btn py-4 px-2 border-b-2 border-transparent font-medium text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200" data-filter="comments">
💬 Kommentare
</button>
<button class="filter-btn py-4 px-2 border-b-2 border-transparent font-medium text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200" data-filter="follows">
👥 Follows
</button>
</nav>
</div>
</div>
<!-- Notifications Container -->
<div id="notifications-container" class="space-y-4">
<!-- Notifications werden hier geladen -->
</div>
<!-- Load More Button -->
<div class="text-center mt-8">
<button id="load-more" class="px-6 py-3 bg-gray-600 text-white rounded-lg hover:bg-gray-700 transition-colors">
Mehr laden
</button>
</div>
</div>
<script>
class NotificationCenter {
constructor() {
this.currentFilter = 'all';
this.currentPage = 1;
this.isLoading = false;
this.hasMore = true;
this.initializeEventListeners();
this.loadNotifications();
}
initializeEventListeners() {
// Filter buttons
document.querySelectorAll('.filter-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const filter = e.target.dataset.filter;
this.switchFilter(filter);
});
});
// Mark all as read
document.getElementById('mark-all-read').addEventListener('click', () => {
this.markAllAsRead();
});
// Load more
document.getElementById('load-more').addEventListener('click', () => {
this.loadMoreNotifications();
});
}
switchFilter(filter) {
this.currentFilter = filter;
this.currentPage = 1;
this.hasMore = true;
// Update filter buttons
document.querySelectorAll('.filter-btn').forEach(btn => {
btn.classList.remove('active', 'border-blue-500', 'text-blue-600', 'dark:text-blue-400');
btn.classList.add('border-transparent', 'text-gray-500', 'dark:text-gray-400');
});
const activeBtn = document.querySelector(`[data-filter="${filter}"]`);
activeBtn.classList.add('active', 'border-blue-500', 'text-blue-600', 'dark:text-blue-400');
activeBtn.classList.remove('border-transparent', 'text-gray-500', 'dark:text-gray-400');
this.loadNotifications();
}
async loadNotifications() {
if (this.isLoading) return;
this.isLoading = true;
try {
const params = new URLSearchParams({
page: this.currentPage,
per_page: 20,
filter: this.currentFilter
});
const response = await fetch(`/api/social/notifications?${params}`);
const result = await response.json();
if (result.success) {
if (this.currentPage === 1) {
document.getElementById('notifications-container').innerHTML = '';
}
this.renderNotifications(result.notifications);
this.hasMore = result.has_more;
this.updateLoadMoreButton();
} else {
this.showMessage('Fehler beim Laden der Benachrichtigungen', 'error');
}
} catch (error) {
console.error('Error loading notifications:', error);
this.showMessage('Fehler beim Laden der Benachrichtigungen', 'error');
} finally {
this.isLoading = false;
}
}
async loadMoreNotifications() {
if (!this.hasMore || this.isLoading) return;
this.currentPage++;
await this.loadNotifications();
}
renderNotifications(notifications) {
const container = document.getElementById('notifications-container');
if (notifications.length === 0 && this.currentPage === 1) {
container.innerHTML = `
<div class="text-center py-12">
<div class="text-6xl mb-4">📭</div>
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">Keine Benachrichtigungen</h3>
<p class="text-gray-600 dark:text-gray-300">
${this.currentFilter === 'unread' ? 'Alle Benachrichtigungen sind gelesen!' : 'Hier werden deine Benachrichtigungen angezeigt.'}
</p>
</div>
`;
return;
}
notifications.forEach(notification => {
const notificationElement = this.createNotificationElement(notification);
container.appendChild(notificationElement);
});
}
createNotificationElement(notification) {
const element = document.createElement('div');
element.className = `notification-item bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6 ${
!notification.is_read ? 'border-l-4 border-blue-500' : ''
}`;
element.dataset.notificationId = notification.id;
const typeIcons = {
'like': '❤️',
'comment': '💬',
'follow': '👥',
'mention': '📢',
'system': '🔔'
};
element.innerHTML = `
<div class="flex items-start space-x-4">
<div class="flex-shrink-0">
<div class="w-12 h-12 bg-gradient-to-r from-blue-500 to-purple-600 rounded-full flex items-center justify-center text-white text-xl">
${typeIcons[notification.type] || '🔔'}
</div>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-start justify-between">
<div class="flex-1">
<p class="text-gray-900 dark:text-white font-medium">
${notification.message}
</p>
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">
${this.formatDate(notification.created_at)}
</p>
</div>
<div class="flex items-center space-x-2 ml-4">
${!notification.is_read ? `
<button class="mark-read-btn px-3 py-1 text-xs bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors"
data-notification-id="${notification.id}">
Als gelesen markieren
</button>
` : ''}
<div class="relative">
<button class="notification-menu-btn p-2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
data-notification-id="${notification.id}">
<i class="fas fa-ellipsis-v"></i>
</button>
<div class="notification-menu hidden absolute right-0 mt-2 w-48 bg-white dark:bg-gray-700 rounded-md shadow-lg z-10">
<button class="delete-notification-btn block w-full text-left px-4 py-2 text-sm text-red-600 hover:bg-gray-100 dark:hover:bg-gray-600"
data-notification-id="${notification.id}">
Löschen
</button>
</div>
</div>
</div>
</div>
</div>
</div>
`;
// Event listeners für die Buttons
const markReadBtn = element.querySelector('.mark-read-btn');
if (markReadBtn) {
markReadBtn.addEventListener('click', (e) => {
e.stopPropagation();
this.markAsRead(notification.id);
});
}
const menuBtn = element.querySelector('.notification-menu-btn');
const menu = element.querySelector('.notification-menu');
menuBtn.addEventListener('click', (e) => {
e.stopPropagation();
menu.classList.toggle('hidden');
});
const deleteBtn = element.querySelector('.delete-notification-btn');
deleteBtn.addEventListener('click', (e) => {
e.stopPropagation();
this.deleteNotification(notification.id);
});
// Click outside to close menu
document.addEventListener('click', () => {
menu.classList.add('hidden');
});
return element;
}
async markAsRead(notificationId) {
try {
const response = await fetch(`/api/social/notifications/${notificationId}/read`, {
method: 'POST'
});
const result = await response.json();
if (result.success) {
const element = document.querySelector(`[data-notification-id="${notificationId}"]`);
element.classList.remove('border-l-4', 'border-blue-500');
const markReadBtn = element.querySelector('.mark-read-btn');
if (markReadBtn) {
markReadBtn.remove();
}
this.showMessage('Als gelesen markiert', 'success');
} else {
this.showMessage('Fehler beim Markieren', 'error');
}
} catch (error) {
console.error('Error marking as read:', error);
this.showMessage('Fehler beim Markieren', 'error');
}
}
async markAllAsRead() {
try {
const response = await fetch('/api/social/notifications/mark-all-read', {
method: 'POST'
});
const result = await response.json();
if (result.success) {
// Remove all unread indicators
document.querySelectorAll('.notification-item').forEach(item => {
item.classList.remove('border-l-4', 'border-blue-500');
const markReadBtn = item.querySelector('.mark-read-btn');
if (markReadBtn) {
markReadBtn.remove();
}
});
this.showMessage('Alle Benachrichtigungen als gelesen markiert', 'success');
} else {
this.showMessage('Fehler beim Markieren aller Benachrichtigungen', 'error');
}
} catch (error) {
console.error('Error marking all as read:', error);
this.showMessage('Fehler beim Markieren aller Benachrichtigungen', 'error');
}
}
async deleteNotification(notificationId) {
if (!confirm('Diese Benachrichtigung wirklich löschen?')) return;
try {
const response = await fetch(`/api/social/notifications/${notificationId}`, {
method: 'DELETE'
});
const result = await response.json();
if (result.success) {
const element = document.querySelector(`[data-notification-id="${notificationId}"]`);
element.remove();
this.showMessage('Benachrichtigung gelöscht', 'success');
} else {
this.showMessage('Fehler beim Löschen', 'error');
}
} catch (error) {
console.error('Error deleting notification:', error);
this.showMessage('Fehler beim Löschen', 'error');
}
}
updateLoadMoreButton() {
const loadMoreBtn = document.getElementById('load-more');
if (this.hasMore) {
loadMoreBtn.style.display = 'block';
loadMoreBtn.textContent = this.isLoading ? 'Lädt...' : 'Mehr laden';
loadMoreBtn.disabled = this.isLoading;
} else {
loadMoreBtn.style.display = 'none';
}
}
formatDate(dateString) {
const date = new Date(dateString);
const now = new Date();
const diffInSeconds = Math.floor((now - date) / 1000);
if (diffInSeconds < 60) return 'Gerade eben';
if (diffInSeconds < 3600) return `vor ${Math.floor(diffInSeconds / 60)} Min`;
if (diffInSeconds < 86400) return `vor ${Math.floor(diffInSeconds / 3600)} Std`;
if (diffInSeconds < 2592000) return `vor ${Math.floor(diffInSeconds / 86400)} Tagen`;
return date.toLocaleDateString('de-DE');
}
showMessage(message, type) {
const toast = document.createElement('div');
toast.className = `fixed top-4 right-4 px-6 py-3 rounded-lg shadow-lg z-50 ${
type === 'success' ? 'bg-green-500 text-white' :
type === 'error' ? 'bg-red-500 text-white' : 'bg-blue-500 text-white'
}`;
toast.textContent = message;
document.body.appendChild(toast);
setTimeout(() => {
toast.remove();
}, 3000);
}
}
// Initialize when DOM is loaded
document.addEventListener('DOMContentLoaded', () => {
new NotificationCenter();
});
</script>
{% endblock %}