notifications
This commit is contained in:
parent
decc6cf778
commit
5c9f68b080
@ -1,186 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useNotifications } from '@/hooks/use-notifications';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { Notification } from '@/lib/types/notification';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
import { Bell, Check, CheckCheck, ExternalLink, Trash2 } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
|
||||
// Source icon mapping
|
||||
const SourceIcons: Record<string, React.ReactNode> = {
|
||||
leantime: <div className="w-8 h-8 rounded-full bg-blue-100 flex items-center justify-center text-blue-600">L</div>,
|
||||
nextcloud: <div className="w-8 h-8 rounded-full bg-blue-100 flex items-center justify-center text-blue-600">N</div>,
|
||||
gitea: <div className="w-8 h-8 rounded-full bg-green-100 flex items-center justify-center text-green-600">G</div>,
|
||||
dolibarr: <div className="w-8 h-8 rounded-full bg-purple-100 flex items-center justify-center text-purple-600">D</div>,
|
||||
moodle: <div className="w-8 h-8 rounded-full bg-orange-100 flex items-center justify-center text-orange-600">M</div>,
|
||||
};
|
||||
|
||||
export default function NotificationsPage() {
|
||||
const { notifications, notificationCount, loading, error, fetchNotifications, markAsRead, markAllAsRead } = useNotifications();
|
||||
const [activeTab, setActiveTab] = useState('all');
|
||||
|
||||
// Filter notifications based on active tab
|
||||
const filteredNotifications = activeTab === 'all'
|
||||
? notifications
|
||||
: notifications.filter(notification => notification.source === activeTab);
|
||||
|
||||
// Group notifications by source for counts
|
||||
const sourceCounts = Object.entries(notificationCount.sources).map(([source, count]) => ({
|
||||
source,
|
||||
count: count.total,
|
||||
unread: count.unread
|
||||
}));
|
||||
|
||||
const handleMarkAsRead = async (notification: Notification) => {
|
||||
if (!notification.isRead) {
|
||||
await markAsRead(notification.id);
|
||||
}
|
||||
};
|
||||
|
||||
const handleMarkAllAsRead = async () => {
|
||||
await markAllAsRead();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container mx-auto py-6">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold tracking-tight">Notifications</h1>
|
||||
<p className="text-muted-foreground">
|
||||
Manage your notifications from all connected services
|
||||
</p>
|
||||
</div>
|
||||
<Button variant="outline" onClick={handleMarkAllAsRead} disabled={notificationCount.unread === 0}>
|
||||
<CheckCheck className="mr-2 h-4 w-4" />
|
||||
Mark all as read
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Tabs defaultValue="all" value={activeTab} onValueChange={setActiveTab}>
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<TabsList>
|
||||
<TabsTrigger value="all" className="relative">
|
||||
All
|
||||
{notificationCount.unread > 0 && (
|
||||
<Badge variant="secondary" className="ml-2">
|
||||
{notificationCount.unread}
|
||||
</Badge>
|
||||
)}
|
||||
</TabsTrigger>
|
||||
|
||||
{sourceCounts.map(({ source, unread }) => (
|
||||
<TabsTrigger key={source} value={source} className="relative capitalize">
|
||||
{source}
|
||||
{unread > 0 && (
|
||||
<Badge variant="secondary" className="ml-2">
|
||||
{unread}
|
||||
</Badge>
|
||||
)}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
</div>
|
||||
|
||||
<TabsContent value={activeTab} className="space-y-4">
|
||||
{loading ? (
|
||||
<Card>
|
||||
<CardContent className="py-10 text-center">
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900 mb-2"></div>
|
||||
<p>Loading notifications...</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : error ? (
|
||||
<Card>
|
||||
<CardContent className="py-10 text-center">
|
||||
<div className="flex flex-col items-center">
|
||||
<p className="text-red-500">{error}</p>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => fetchNotifications()}
|
||||
className="mt-4"
|
||||
>
|
||||
Retry
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : filteredNotifications.length === 0 ? (
|
||||
<Card>
|
||||
<CardContent className="py-10 text-center">
|
||||
<div className="flex flex-col items-center">
|
||||
<Bell className="h-12 w-12 text-gray-400 mb-4" />
|
||||
<h3 className="font-medium text-lg">No notifications</h3>
|
||||
<p className="text-muted-foreground">
|
||||
You don't have any {activeTab !== 'all' ? `${activeTab} ` : ''}notifications right now.
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
filteredNotifications.map((notification) => (
|
||||
<Card
|
||||
key={notification.id}
|
||||
className={notification.isRead ? 'bg-white' : 'bg-blue-50 border-blue-200'}
|
||||
>
|
||||
<CardContent className="p-4">
|
||||
<div className="flex">
|
||||
<div className="mr-4 mt-1">
|
||||
{SourceIcons[notification.source] || <Bell className="h-8 w-8" />}
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="flex items-start justify-between">
|
||||
<div>
|
||||
<h3 className="font-medium">
|
||||
{notification.title}
|
||||
{!notification.isRead && (
|
||||
<Badge className="ml-2 bg-blue-500">New</Badge>
|
||||
)}
|
||||
</h3>
|
||||
<p className="text-sm text-muted-foreground capitalize">
|
||||
From {notification.source} • {formatDistanceToNow(new Date(notification.timestamp), { addSuffix: true })}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex space-x-2 ml-4">
|
||||
{!notification.isRead && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => handleMarkAsRead(notification)}
|
||||
title="Mark as read"
|
||||
>
|
||||
<Check className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
{notification.link && (
|
||||
<Link href={notification.link} target="_blank">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
title="Open link"
|
||||
>
|
||||
<ExternalLink className="h-4 w-4" />
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<p className="mt-2">{notification.message}</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))
|
||||
)}
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -34,8 +34,8 @@ export const NotificationBadge = memo(function NotificationBadge({ className }:
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
// Take the latest 5 notifications for the dropdown
|
||||
const recentNotifications = notifications.slice(0, 5);
|
||||
// Take the latest 10 notifications for the dropdown
|
||||
const recentNotifications = notifications.slice(0, 10);
|
||||
|
||||
return (
|
||||
<div className={`relative ${className || ''}`}>
|
||||
@ -55,7 +55,7 @@ export const NotificationBadge = memo(function NotificationBadge({ className }:
|
||||
<span className="sr-only">Notifications</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-80">
|
||||
<DropdownMenuContent align="end" className="w-80 max-h-[80vh] overflow-y-auto">
|
||||
<div className="flex items-center justify-between p-4">
|
||||
<h3 className="font-medium">Notifications</h3>
|
||||
{notificationCount.unread > 0 && (
|
||||
@ -116,16 +116,6 @@ export const NotificationBadge = memo(function NotificationBadge({ className }:
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
<div className="p-2 text-center">
|
||||
<Link
|
||||
href="/notifications"
|
||||
className="text-xs text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
View all notifications
|
||||
</Link>
|
||||
</div>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user