Add notification pagination to backend and frontend

This commit is contained in:
Sami Abuzakuk
2025-10-22 22:37:12 +02:00
parent d3df001397
commit 16989ed518
3 changed files with 82 additions and 12 deletions

View File

@@ -1,5 +1,5 @@
from datetime import datetime
from fastapi import FastAPI
from fastapi import FastAPI, Query
from fastapi.exceptions import HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
@@ -144,11 +144,18 @@ def remove_subscription(subscription_id: int):
@app.get("/subscriptions/{subscription_id}/notifications")
def list_subscription_notifications(subscription_id: int):
def list_subscription_notifications(
subscription_id: int,
limit: int = Query(20, ge=1, le=100),
offset: int = Query(0, ge=0),
):
db = SessionLocal()
notifications = (
db.query(Notification)
.filter(Notification.subscription_id == subscription_id)
.order_by(Notification.created_at.desc())
.limit(limit)
.offset(offset)
.all()
)
db.close()

View File

@@ -205,11 +205,15 @@ export async function deleteSubscription(subscriptionId: number): Promise<void>
}
}
// Get all subscription notifications
// Get subscription notifications with pagination
export async function fetchSubscriptionNotifications(
subscriptionId: string
subscriptionId: string,
limit: number = 20,
offset: number = 0
): Promise<Notification[]> {
const response = await fetch(`${API_URL}/subscriptions/${subscriptionId}/notifications`);
const response = await fetch(
`${API_URL}/subscriptions/${subscriptionId}/notifications?limit=${limit}&offset=${offset}`
);
if (!response.ok) {
throw new Error('Failed to fetch subscription notifications');
}

View File

@@ -1,15 +1,34 @@
<script lang="ts">
import { deleteNotification, addNotification, setViewed } from '$lib/api';
import { deleteNotification, setViewed, fetchSubscriptionNotifications } from '$lib/api';
import type { Notification, Subscription } from '$lib/api';
export let data: { notifications: Notification[]; subscription: Subscription };
let notifications: Notification[] = data.notifications;
let newNotificationTitle = '';
let newNotificationMessage = '';
let newNotificationPriority = 3;
let selectedNotification: Notification | null = null;
// Delete all notifications for this subscription
async function handleDeleteAllNotifications() {
if (notifications.length === 0) return;
const confirmed = window.confirm(
'Are you sure you want to delete all notifications for this subscription?'
);
if (!confirmed) return;
try {
await Promise.all(notifications.map((notification) => deleteNotification(notification.id)));
notifications = [];
window.showNotification('success', 'All notifications deleted successfully.');
} catch (error) {
window.showNotification('error', 'Failed to delete all notifications - ' + error);
}
}
// Pagination state
let limit = 20;
let offset = notifications.length;
let loadingMore = false;
let allLoaded = notifications.length < limit;
async function openNotificationPopup(notification: Notification) {
if (!notification.viewed) {
await setViewed(notification.id);
@@ -50,6 +69,26 @@
window.showNotification('error', 'Failed to mark all notifications as viewed.');
}
}
// Load more notifications
async function loadMoreNotifications() {
loadingMore = true;
try {
const more = await fetchSubscriptionNotifications(
data.subscription.id.toString(),
limit,
offset
);
notifications = [...notifications, ...more];
offset += more.length;
if (more.length < limit) {
allLoaded = true;
}
} catch (error) {
window.showNotification('error', 'Failed to load more notifications');
}
loadingMore = false;
}
</script>
<main class="p-4">
@@ -57,9 +96,18 @@
<div class="flex justify-between items-center mb-4">
<a href="/notifications" class="text-blue-500 hover:underline">← Return to Subscriptions</a>
<button class="bg-blue-500 text-white px-4 py-2 rounded" on:click={markAllViewed}>
Mark All Viewed
</button>
<div class="flex gap-2">
<button class="bg-blue-500 text-white px-4 py-2 rounded" on:click={markAllViewed}>
Mark All Viewed
</button>
<button
class="bg-red-500 text-white px-4 py-2 rounded"
on:click={handleDeleteAllNotifications}
disabled={notifications.length === 0}
>
Delete All Notifications
</button>
</div>
</div>
{#if notifications.length === 0}
@@ -89,6 +137,17 @@
{/each}
</ul>
{/if}
{#if !allLoaded && notifications.length > 0}
<div class="flex justify-center mt-6">
<button
class="bg-gray-200 px-4 py-2 rounded hover:bg-gray-300"
on:click={loadMoreNotifications}
disabled={loadingMore}
>
{loadingMore ? 'Loading...' : 'Load More'}
</button>
</div>
{/if}
{#if selectedNotification}
<div class="fixed inset-0 bg-opacity-30 backdrop-blur-sm flex items-center justify-center z-50">
<div class="bg-white p-6 rounded shadow-lg w-3/4 max-w-2xl max-h-[80vh] overflow-y-auto">