"use client"; import { useState } from "react"; import { useSearchParams } from "next/navigation"; import { ConfirmDialog } from "~/components/ui/ConfirmDialog.js"; import { trpc } from "~/lib/trpc/client.js"; import { usePermissions } from "~/hooks/usePermissions.js"; import { TaskCard } from "./TaskCard.js"; import { ReminderModal } from "./ReminderModal.js"; import { CreateTaskModal } from "./CreateTaskModal.js"; type TabKey = "all" | "notifications" | "tasks" | "reminders" | "approvals"; function relativeTime(date: Date): string { const now = Date.now(); const diff = now - date.getTime(); const seconds = Math.floor(diff / 1000); if (seconds < 60) return "just now"; const minutes = Math.floor(seconds / 60); if (minutes < 60) return `${minutes}m ago`; const hours = Math.floor(minutes / 60); if (hours < 24) return `${hours}h ago`; const days = Math.floor(hours / 24); if (days < 30) return `${days}d ago`; const months = Math.floor(days / 30); return `${months}mo ago`; } export function NotificationCenterClient() { const searchParams = useSearchParams(); const initialTab = (searchParams.get("tab") as TabKey) || "all"; const [activeTab, setActiveTab] = useState(initialTab); const { canEdit } = usePermissions(); const [showTaskModal, setShowTaskModal] = useState(false); const [confirmDeleteReminder, setConfirmDeleteReminder] = useState(null); const [reminderModal, setReminderModal] = useState<{ open: boolean; reminder: { id: string; title: string; body?: string | null; remindAt?: string | Date | null; recurrence?: string | null; link?: string | null; } | null; }>({ open: false, reminder: null }); const utils = trpc.useUtils(); // Queries const { data: allNotifications = [], isLoading: loadingAll } = trpc.notification.list.useQuery( { limit: 50 }, { enabled: activeTab === "all", refetchInterval: 30_000 }, ); const { data: notifications = [], isLoading: loadingNotifications } = trpc.notification.list.useQuery( { category: "NOTIFICATION", limit: 50 }, { enabled: activeTab === "notifications", refetchInterval: 30_000 }, ); const { data: tasks = [], isLoading: loadingTasks } = trpc.notification.listTasks.useQuery( { limit: 50 }, { enabled: activeTab === "tasks", refetchInterval: 30_000 }, ); const { data: reminders = [], isLoading: loadingReminders } = trpc.notification.listReminders.useQuery( { limit: 50 }, { enabled: activeTab === "reminders", refetchInterval: 30_000 }, ); const { data: approvals = [], isLoading: loadingApprovals } = trpc.notification.list.useQuery( { category: "APPROVAL", limit: 50 }, { enabled: activeTab === "approvals", refetchInterval: 30_000 }, ); const { data: taskCounts } = trpc.notification.taskCounts.useQuery(undefined, { refetchInterval: 30_000, }); // Mutations const markRead = trpc.notification.markRead.useMutation({ onSuccess: () => { void utils.notification.unreadCount.invalidate(); void utils.notification.list.invalidate(); }, }); const updateTaskStatus = trpc.notification.updateTaskStatus.useMutation({ onSuccess: () => { void utils.notification.taskCounts.invalidate(); void utils.notification.listTasks.invalidate(); void utils.notification.list.invalidate(); void utils.notification.unreadCount.invalidate(); }, }); const deleteReminder = trpc.notification.deleteReminder.useMutation({ onSuccess: () => { void utils.notification.listReminders.invalidate(); void utils.notification.list.invalidate(); }, }); function handleTaskStatusChange(id: string, status: string) { updateTaskStatus.mutate({ id, status: status as "OPEN" | "IN_PROGRESS" | "DONE" | "DISMISSED" }); } function handleMarkAllRead() { markRead.mutate({}); } const tabs: { key: TabKey; label: string; count?: number }[] = [ { key: "all", label: "All" }, { key: "notifications", label: "Notifications" }, { key: "tasks", label: "Tasks", count: (taskCounts?.open ?? 0) + (taskCounts?.inProgress ?? 0) }, { key: "reminders", label: "Reminders" }, { key: "approvals", label: "Approvals" }, ]; const isLoading = (activeTab === "all" && loadingAll) || (activeTab === "notifications" && loadingNotifications) || (activeTab === "tasks" && loadingTasks) || (activeTab === "reminders" && loadingReminders) || (activeTab === "approvals" && loadingApprovals); return (
{/* Page header */}

Notification Center

{canEdit && activeTab === "tasks" && ( )} {activeTab === "reminders" && ( )}
{/* Tabs */}
{tabs.map((tab) => ( ))}
{/* Loading state */} {isLoading && (
{[...Array(5)].map((_, i) => (
))}
)} {/* Content */} {!isLoading && (
{/* All / Notifications / Approvals tabs */} {(activeTab === "all" || activeTab === "notifications" || activeTab === "approvals") && ( <> {(activeTab === "all" ? allNotifications : activeTab === "notifications" ? notifications : approvals).length === 0 ? (
{activeTab === "approvals" ? "No pending approvals" : "No notifications"}
) : ( (activeTab === "all" ? allNotifications : activeTab === "notifications" ? notifications : approvals).map((n) => { const isUnread = n.readAt === null; const isTask = n.category === "TASK" || n.category === "APPROVAL"; if (isTask && n.taskStatus) { return ( ); } return ( ); }) )} )} {/* Tasks tab */} {activeTab === "tasks" && ( <> {tasks.length === 0 ? (
No tasks
) : ( tasks.map((t) => ( )) )} )} {/* Reminders tab */} {activeTab === "reminders" && ( <> {reminders.length === 0 ? (
No reminders. Create one to get started.
) : ( reminders.map((r) => (

{r.title}

{r.body && (

{r.body}

)}
{r.nextRemindAt && ( {new Date(r.nextRemindAt).toLocaleDateString("en-GB", { day: "2-digit", month: "short", year: "numeric", hour: "2-digit", minute: "2-digit", })} )} {r.recurrence && ( {r.recurrence} )}
)) )} )}
)} {/* Reminder Modal */} {reminderModal.open && ( setReminderModal({ open: false, reminder: null })} onSuccess={() => setReminderModal({ open: false, reminder: null })} /> )} {/* Create Task Modal */} {showTaskModal && ( setShowTaskModal(false)} onSuccess={() => setShowTaskModal(false)} /> )} {confirmDeleteReminder && ( { deleteReminder.mutate({ id: confirmDeleteReminder }); setConfirmDeleteReminder(null); }} onCancel={() => setConfirmDeleteReminder(null)} /> )}
); }