381 lines
15 KiB
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 %} |