Introduces a NotificationProvider context to centralize notification logic and state, refactoring UnifiedNotifications to use this context. Adds a privacy toggle to AnalyticsDashboard and RevenueChart to allow hiding sensitive numbers. Updates layout to wrap the app with NotificationProvider.
286 lines
12 KiB
TypeScript
286 lines
12 KiB
TypeScript
"use client";
|
|
|
|
import React, { useState } from "react";
|
|
import { useRouter } from "next/navigation";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { Button } from "@/components/ui/button";
|
|
import { BellRing, Package, MessageCircle } from "lucide-react";
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuSeparator,
|
|
DropdownMenuTrigger,
|
|
} from "@/components/ui/dropdown-menu";
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
import { useNotifications } from "@/lib/notification-context";
|
|
|
|
export default function UnifiedNotifications() {
|
|
const router = useRouter();
|
|
const [activeTab, setActiveTab] = useState<string>("all");
|
|
|
|
// Get notification state from context
|
|
const {
|
|
unreadCounts,
|
|
chatMetadata,
|
|
newOrders,
|
|
clearOrderNotifications,
|
|
totalNotifications,
|
|
loading,
|
|
} = useNotifications();
|
|
|
|
// Navigation handlers
|
|
const handleChatClick = (chatId: string) => {
|
|
router.push(`/dashboard/chats/${chatId}`);
|
|
};
|
|
|
|
const handleOrderClick = (orderId: string) => {
|
|
router.push(`/dashboard/orders/${orderId}`);
|
|
};
|
|
|
|
// Format the price as currency
|
|
const formatPrice = (price: number) => {
|
|
return `£${price.toFixed(2)}`;
|
|
};
|
|
|
|
return (
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<Button variant="ghost" size="icon" className="relative" disabled={loading}>
|
|
<BellRing className="h-5 w-5" />
|
|
{totalNotifications > 0 && (
|
|
<Badge
|
|
variant="destructive"
|
|
className="absolute -top-1 -right-1 px-1.5 py-0.5 text-xs"
|
|
>
|
|
{totalNotifications}
|
|
</Badge>
|
|
)}
|
|
</Button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="end" className="w-80">
|
|
<div className="p-2 border-b">
|
|
<Tabs defaultValue="all" value={activeTab} onValueChange={setActiveTab}>
|
|
<TabsList className="grid w-full grid-cols-3">
|
|
<TabsTrigger value="all" className="text-xs">
|
|
All
|
|
{totalNotifications > 0 && (
|
|
<Badge variant="secondary" className="ml-1">
|
|
{totalNotifications}
|
|
</Badge>
|
|
)}
|
|
</TabsTrigger>
|
|
<TabsTrigger value="messages" className="text-xs">
|
|
Messages
|
|
{unreadCounts.totalUnread > 0 && (
|
|
<Badge variant="secondary" className="ml-1">
|
|
{unreadCounts.totalUnread}
|
|
</Badge>
|
|
)}
|
|
</TabsTrigger>
|
|
<TabsTrigger value="orders" className="text-xs">
|
|
Orders
|
|
{newOrders.length > 0 && (
|
|
<Badge variant="secondary" className="ml-1">
|
|
{newOrders.length}
|
|
</Badge>
|
|
)}
|
|
</TabsTrigger>
|
|
</TabsList>
|
|
|
|
<TabsContent value="all" className="m-0">
|
|
{totalNotifications === 0 ? (
|
|
<div className="p-4 flex items-center justify-center">
|
|
<p className="text-sm text-muted-foreground">No new notifications</p>
|
|
</div>
|
|
) : (
|
|
<div className="max-h-96 overflow-y-auto">
|
|
{/* Messages Section */}
|
|
{unreadCounts.totalUnread > 0 && (
|
|
<>
|
|
<div className="px-3 py-2 text-xs font-medium bg-muted/50">
|
|
Unread Messages
|
|
</div>
|
|
{Object.entries(unreadCounts.chatCounts).slice(0, 3).map(([chatId, count]) => (
|
|
<DropdownMenuItem
|
|
key={`chat-${chatId}`}
|
|
className="p-3 cursor-pointer"
|
|
onClick={() => handleChatClick(chatId)}
|
|
>
|
|
<div className="flex items-center justify-between w-full">
|
|
<div className="flex items-center gap-2">
|
|
<MessageCircle className="h-4 w-4 text-blue-500" />
|
|
<div>
|
|
<p className="font-medium">
|
|
Customer {chatMetadata[chatId]?.buyerId.slice(-4) || 'Unknown'}
|
|
</p>
|
|
<p className="text-xs text-muted-foreground">
|
|
{count} new {count === 1 ? 'message' : 'messages'}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<Badge variant="secondary">{count}</Badge>
|
|
</div>
|
|
</DropdownMenuItem>
|
|
))}
|
|
{Object.keys(unreadCounts.chatCounts).length > 3 && (
|
|
<div className="px-3 py-2 text-xs text-center text-muted-foreground">
|
|
+ {Object.keys(unreadCounts.chatCounts).length - 3} more unread chats
|
|
</div>
|
|
)}
|
|
</>
|
|
)}
|
|
|
|
{/* Orders Section */}
|
|
{newOrders.length > 0 && (
|
|
<>
|
|
<div className="px-3 py-2 text-xs font-medium bg-muted/50 flex justify-between items-center">
|
|
<span>New Paid Orders</span>
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
clearOrderNotifications();
|
|
}}
|
|
className="h-6 text-xs"
|
|
>
|
|
Clear
|
|
</Button>
|
|
</div>
|
|
{newOrders.slice(0, 3).map((order) => (
|
|
<DropdownMenuItem
|
|
key={`order-${order._id}`}
|
|
className="p-3 cursor-pointer"
|
|
onClick={() => handleOrderClick(order._id)}
|
|
>
|
|
<div className="flex items-center justify-between w-full">
|
|
<div className="flex items-center gap-2">
|
|
<Package className="h-4 w-4 text-green-500" />
|
|
<div>
|
|
<p className="font-medium">Order #{order.orderId}</p>
|
|
<p className="text-xs text-muted-foreground">
|
|
{formatPrice(order.totalPrice)}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<Badge className="bg-green-500 hover:bg-green-600">Paid</Badge>
|
|
</div>
|
|
</DropdownMenuItem>
|
|
))}
|
|
{newOrders.length > 3 && (
|
|
<div className="px-3 py-2 text-xs text-center text-muted-foreground">
|
|
+ {newOrders.length - 3} more new orders
|
|
</div>
|
|
)}
|
|
</>
|
|
)}
|
|
</div>
|
|
)}
|
|
</TabsContent>
|
|
|
|
<TabsContent value="messages" className="m-0">
|
|
{unreadCounts.totalUnread === 0 ? (
|
|
<div className="p-4 flex items-center justify-center">
|
|
<p className="text-sm text-muted-foreground">No unread messages</p>
|
|
</div>
|
|
) : (
|
|
<>
|
|
<div className="max-h-96 overflow-y-auto">
|
|
{Object.entries(unreadCounts.chatCounts).map(([chatId, count]) => (
|
|
<DropdownMenuItem
|
|
key={`chat-tab-${chatId}`}
|
|
className="p-3 cursor-pointer"
|
|
onClick={() => handleChatClick(chatId)}
|
|
>
|
|
<div className="flex items-center justify-between w-full">
|
|
<div className="flex items-center gap-2">
|
|
<MessageCircle className="h-4 w-4 text-blue-500" />
|
|
<div>
|
|
<p className="font-medium">
|
|
Customer {chatMetadata[chatId]?.buyerId.slice(-4) || 'Unknown'}
|
|
</p>
|
|
<p className="text-xs text-muted-foreground">
|
|
{count} new {count === 1 ? 'message' : 'messages'}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<Badge variant="secondary">{count}</Badge>
|
|
</div>
|
|
</DropdownMenuItem>
|
|
))}
|
|
</div>
|
|
<div className="p-2 border-t">
|
|
<Button
|
|
variant="outline"
|
|
className="w-full"
|
|
onClick={() => router.push('/dashboard/chats')}
|
|
>
|
|
View All Chats
|
|
</Button>
|
|
</div>
|
|
</>
|
|
)}
|
|
</TabsContent>
|
|
|
|
<TabsContent value="orders" className="m-0">
|
|
{newOrders.length === 0 ? (
|
|
<div className="p-4 flex items-center justify-center">
|
|
<p className="text-sm text-muted-foreground">No new paid orders</p>
|
|
</div>
|
|
) : (
|
|
<>
|
|
<div className="px-3 py-2 text-xs font-medium bg-muted/50 flex justify-between items-center">
|
|
<span>New Paid Orders</span>
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
clearOrderNotifications();
|
|
}}
|
|
className="h-6 text-xs"
|
|
>
|
|
Clear
|
|
</Button>
|
|
</div>
|
|
<div className="max-h-96 overflow-y-auto">
|
|
{newOrders.map((order) => (
|
|
<DropdownMenuItem
|
|
key={`order-tab-${order._id}`}
|
|
className="p-3 cursor-pointer"
|
|
onClick={() => handleOrderClick(order._id)}
|
|
>
|
|
<div className="flex items-center justify-between w-full">
|
|
<div className="flex items-center gap-2">
|
|
<Package className="h-4 w-4 text-green-500" />
|
|
<div>
|
|
<p className="font-medium">Order #{order.orderId}</p>
|
|
<p className="text-xs text-muted-foreground">
|
|
{formatPrice(order.totalPrice)}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<Badge className="bg-green-500 hover:bg-green-600">Paid</Badge>
|
|
</div>
|
|
</DropdownMenuItem>
|
|
))}
|
|
</div>
|
|
<div className="p-2 border-t">
|
|
<Button
|
|
variant="outline"
|
|
className="w-full"
|
|
onClick={() => router.push('/dashboard/orders')}
|
|
>
|
|
View All Orders
|
|
</Button>
|
|
</div>
|
|
</>
|
|
)}
|
|
</TabsContent>
|
|
</Tabs>
|
|
</div>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
);
|
|
}
|