Skip to content

Commit 6e9be14

Browse files
committed
UI: Add notification badges for pending inquiries in Admin Dashboard, Sidebar, and Bottom Nav
1 parent b3c04c5 commit 6e9be14

4 files changed

Lines changed: 66 additions & 8 deletions

File tree

web/src/actions/tickets.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,3 +334,13 @@ export async function archiveTicket(ticketId: string) {
334334
return { success: false, error: 'Failed to archive' };
335335
}
336336
}
337+
export async function getOpenTicketsCount() {
338+
try {
339+
if (!await isAdmin()) return 0;
340+
return await prisma.ticket.count({
341+
where: { status: 'OPEN' }
342+
});
343+
} catch (error) {
344+
return 0;
345+
}
346+
}

web/src/app/admin/page.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,19 @@ export default async function AdminDashboard() {
7777
<Link
7878
key={i}
7979
href={stat.href}
80-
className="bg-white p-2 md:px-5 md:py-3 rounded-xl md:rounded-2xl shadow-sm border border-gray-100 flex items-center space-x-2 md:space-x-3 hover:shadow-md hover:border-gray-200 transition-all active:scale-95"
80+
className="bg-white p-2 md:px-5 md:py-3 rounded-xl md:rounded-2xl shadow-sm border border-gray-100 flex items-center space-x-2 md:space-x-3 hover:shadow-md hover:border-gray-200 transition-all active:scale-95 group relative"
8181
>
82-
<stat.icon className={`w-3.5 h-3.5 md:w-5 md:h-5 ${stat.color} flex-none`} />
82+
<div className="relative">
83+
<stat.icon className={`w-3.5 h-3.5 md:w-5 md:h-5 ${stat.color} flex-none transition-transform group-hover:scale-110`} />
84+
{stat.label === 'Pending' && stat.value > 0 && (
85+
<span className="absolute -top-1.5 -right-1.5 flex h-4 w-4 md:h-5 md:w-5 items-center justify-center rounded-full bg-red-600 text-[6px] md:text-[9px] font-black text-white shadow-lg shadow-red-500/40 ring-2 ring-white animate-in zoom-in duration-300">
86+
{stat.value}
87+
</span>
88+
)}
89+
</div>
8390
<div className="min-w-0">
8491
<p className="text-[7px] md:text-[10px] uppercase tracking-tighter text-gray-400 font-black leading-none mb-0.5 truncate">{stat.label}</p>
85-
<p className="text-sm md:text-lg font-black text-gray-800 leading-none">{stat.value}</p>
92+
<p className="text-sm md:text-lg font-black text-gray-800 leading-none tracking-tight">{stat.value}</p>
8693
</div>
8794
</Link>
8895
))}

web/src/components/admin/AdminBottomNav.tsx

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,28 @@ import {
1111
Ticket
1212
} from 'lucide-react';
1313

14+
import { useState, useEffect } from 'react';
15+
import { getOpenTicketsCount } from '@/actions/tickets';
16+
1417
const ADMIN_NAV_ITEMS = [
1518
{ label: 'Dash', href: '/admin', icon: LayoutDashboard },
1619
{ label: 'Leela', href: '/admin/leela', icon: Footprints },
1720
{ label: 'Bodha', href: '/admin/bodhakatha', icon: Lightbulb },
1821
{ label: 'Glossary', href: '/admin/glossary', icon: Book },
19-
{ label: 'Inquiry', href: '/admin/tickets', icon: Ticket },
22+
{ label: 'Inquiry', href: '/admin/tickets', icon: Ticket, isTickets: true },
2023
];
2124

2225
export default function AdminBottomNav() {
2326
const pathname = usePathname();
27+
const [openCount, setOpenCount] = useState(0);
28+
29+
useEffect(() => {
30+
getOpenTicketsCount().then(setOpenCount);
31+
const interval = setInterval(() => {
32+
getOpenTicketsCount().then(setOpenCount);
33+
}, 120000);
34+
return () => clearInterval(interval);
35+
}, []);
2436

2537
return (
2638
<nav className="fixed bottom-0 left-0 right-0 bg-gray-900 border-t border-white/10 z-[100] pb-safe lg:hidden">
@@ -35,10 +47,17 @@ export default function AdminBottomNav() {
3547
<Link
3648
key={item.href}
3749
href={item.href}
38-
className={`flex flex-col items-center justify-center w-full h-full space-y-1 transition-all active:scale-95 ${isActive ? 'text-ochre' : 'text-gray-500'
50+
className={`flex flex-col items-center justify-center w-full h-full space-y-1 transition-all active:scale-95 relative ${isActive ? 'text-ochre' : 'text-gray-500'
3951
}`}
4052
>
41-
<Icon className={`w-5 h-5 ${isActive ? 'animate-pulse' : ''}`} />
53+
<div className="relative">
54+
<Icon className={`w-5 h-5 ${isActive ? 'animate-pulse' : ''}`} />
55+
{item.isTickets && openCount > 0 && (
56+
<span className="absolute -top-1.5 -right-1.5 flex h-3.5 w-3.5 items-center justify-center rounded-full bg-red-600 text-[7px] font-black text-white shadow-lg ring-1 ring-gray-900 animate-in zoom-in duration-300">
57+
{openCount}
58+
</span>
59+
)}
60+
</div>
4261
<span className="text-[9px] font-bold uppercase tracking-tighter">{item.label}</span>
4362
</Link>
4463
);

web/src/components/admin/AdminSidebar.tsx

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,15 @@ import {
1616
} from 'lucide-react';
1717
import { UserButton } from '@clerk/nextjs';
1818

19+
import { useState, useEffect } from 'react';
20+
import { getOpenTicketsCount } from '@/actions/tickets';
21+
1922
const ADMIN_NAV = [
2023
{ label: 'Dashboard', href: '/admin', icon: LayoutDashboard },
2124
{ label: 'Leela Stories', href: '/admin/leela', icon: Footprints },
2225
{ label: 'Bodhakatha', href: '/admin/bodhakatha', icon: Lightbulb },
2326
{ label: 'Glossary', href: '/admin/glossary', icon: Book },
24-
{ label: 'Seeker Inquiries', href: '/admin/tickets', icon: Ticket },
27+
{ label: 'Seeker Inquiries', href: '/admin/tickets', icon: Ticket, isTickets: true },
2528
];
2629

2730
export default function AdminSidebar({
@@ -32,6 +35,18 @@ export default function AdminSidebar({
3235
onClose?: () => void;
3336
}) {
3437
const pathname = usePathname();
38+
const [openCount, setOpenCount] = useState(0);
39+
40+
useEffect(() => {
41+
getOpenTicketsCount().then(setOpenCount);
42+
43+
// Refresh every 2 mins
44+
const interval = setInterval(() => {
45+
getOpenTicketsCount().then(setOpenCount);
46+
}, 120000);
47+
48+
return () => clearInterval(interval);
49+
}, []);
3550

3651
return (
3752
<>
@@ -92,7 +107,14 @@ export default function AdminSidebar({
92107
<Icon className={`w-5 h-5 ${isActive ? 'text-white' : 'text-gray-400 group-hover:text-ochre'} transition-colors`} />
93108
<span className="font-semibold text-sm">{item.label}</span>
94109
</div>
95-
{isActive && <ChevronRight className="w-4 h-4 opacity-50" />}
110+
<div className="flex items-center gap-2">
111+
{item.isTickets && openCount > 0 && (
112+
<span className={`flex h-5 min-w-5 items-center justify-center rounded-full px-1.5 text-[10px] font-black shadow-lg animate-in zoom-in duration-300 ${isActive ? 'bg-white text-ochre shadow-white/20' : 'bg-red-600 text-white shadow-red-500/30'}`}>
113+
{openCount}
114+
</span>
115+
)}
116+
{isActive && <ChevronRight className="w-4 h-4 opacity-50" />}
117+
</div>
96118
</Link>
97119
);
98120
})}

0 commit comments

Comments
 (0)