import type { ClientNotificationFilters, ClientPaginationState, Notifications, } from "@pkg/logic/domains/notifications/data"; import { archiveSC, deleteNotificationsSC, getNotificationsSQ, getUnreadCountSQ, markAllReadSC, markReadSC, markUnreadSC, unarchiveSC, } from "./notifications.remote"; import { user } from "$lib/global.stores"; import { toast } from "svelte-sonner"; import { get } from "svelte/store"; class NotificationViewModel { notifications = $state([] as Notifications); loading = $state(false); selectedIds = $state(new Set()); pagination = $state({ page: 1, pageSize: 20, total: 0, totalPages: 0, sortBy: "createdAt", sortOrder: "desc", }); filters = $state({ userId: get(user)?.id!, isArchived: false, }); unreadCount = $state(0); private getFetchQueryInput() { return { filters: { userId: this.filters.userId, isRead: this.filters.isRead, isArchived: this.filters.isArchived, type: this.filters.type, category: this.filters.category, priority: this.filters.priority, search: this.filters.search, }, pagination: { page: this.pagination.page, pageSize: this.pagination.pageSize, sortBy: this.pagination.sortBy, sortOrder: this.pagination.sortOrder, }, }; } private async runCommand( fn: (payload: { notificationIds: number[] }) => Promise, notificationIds: number[], errorMessage: string, after: Array<() => Promise>, ) { try { const result = await fn({ notificationIds }); if (result?.error) { toast.error(result.error.message || errorMessage, { description: result.error.description || "Please try again later", }); return; } for (const action of after) { await action(); } } catch (error) { toast.error(errorMessage, { description: error instanceof Error ? error.message : "Please try again later", }); } } async fetchNotifications() { this.loading = true; try { const result = await getNotificationsSQ(this.getFetchQueryInput()); if (result?.error || !result?.data) { toast.error( result?.error?.message || "Failed to fetch notifications", { description: result?.error?.description || "Please try again later", }, ); return; } this.notifications = result.data.data as Notifications; this.pagination.total = result.data.total; this.pagination.totalPages = result.data.totalPages; } catch (error) { toast.error("Failed to fetch notifications", { description: error instanceof Error ? error.message : "Please try again later", }); } finally { this.loading = false; } } async markAsRead(notificationIds: number[]) { await this.runCommand(markReadSC, notificationIds, "Failed to mark as read", [ () => this.fetchNotifications(), () => this.fetchUnreadCount(), ]); } async markAsUnread(notificationIds: number[]) { await this.runCommand( markUnreadSC, notificationIds, "Failed to mark as unread", [() => this.fetchNotifications(), () => this.fetchUnreadCount()], ); } async archive(notificationIds: number[]) { await this.runCommand(archiveSC, notificationIds, "Failed to archive", [ () => this.fetchNotifications(), ]); } async unarchive(notificationIds: number[]) { await this.runCommand(unarchiveSC, notificationIds, "Failed to unarchive", [ () => this.fetchNotifications(), ]); } async deleteNotifications(notificationIds: number[]) { await this.runCommand( deleteNotificationsSC, notificationIds, "Failed to delete", [() => this.fetchNotifications(), () => this.fetchUnreadCount()], ); } async markAllAsRead() { try { const result = await markAllReadSC({}); if (result?.error) { toast.error(result.error.message || "Failed to mark all as read", { description: result.error.description || "Please try again later", }); return; } await this.fetchNotifications(); await this.fetchUnreadCount(); } catch (error) { toast.error("Failed to mark all as read", { description: error instanceof Error ? error.message : "Please try again later", }); } } async fetchUnreadCount() { try { const result = await getUnreadCountSQ(); if (result?.error) { return; } if (result?.data !== undefined && result?.data !== null) { this.unreadCount = result.data as number; } } catch { // Intentionally silent - unread count is non-critical UI data. } } toggleSelection(id: number) { if (this.selectedIds.has(id)) { this.selectedIds.delete(id); } else { this.selectedIds.add(id); } } selectAll() { this.notifications.forEach((n) => this.selectedIds.add(n.id)); } clearSelection() { this.selectedIds.clear(); } goToPage(page: number) { this.pagination.page = page; this.fetchNotifications(); } setPageSize(pageSize: number) { this.pagination.pageSize = pageSize; this.pagination.page = 1; this.fetchNotifications(); } setSorting( sortBy: ClientPaginationState["sortBy"], sortOrder: ClientPaginationState["sortOrder"], ) { this.pagination.sortBy = sortBy; this.pagination.sortOrder = sortOrder; this.pagination.page = 1; this.fetchNotifications(); } setFilters(newFilters: Partial) { this.filters = { ...this.filters, ...newFilters }; this.pagination.page = 1; this.fetchNotifications(); } clearFilters() { this.filters = { userId: get(user)?.id!, isArchived: false }; this.pagination.page = 1; this.fetchNotifications(); } } export const notificationViewModel = new NotificationViewModel();