All checks were successful
Build Frontend / build (push) Successful in 1m10s
Refined color scheme in AnimatedStatsSection to use indigo instead of pink, and improved gradient backgrounds. In dashboard/content.tsx, updated TopProduct price to support arrays and display revenue per product. UnifiedNotifications received minor style and layout adjustments for better consistency and usability.
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" collisionPadding={10}>
|
|
<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>
|
|
);
|
|
}
|