Some checks failed
Build Frontend / build (push) Failing after 7s
Introduces a modular dashboard system with draggable, configurable widgets including revenue, low stock, recent customers, and pending chats. Adds a dashboard editor for layout customization, widget visibility, and settings. Refactors dashboard content to use the new widget system and improves UI consistency and interactivity.
173 lines
7.5 KiB
TypeScript
173 lines
7.5 KiB
TypeScript
"use client"
|
|
|
|
import { useState, useEffect } from "react"
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
|
import { Button } from "@/components/ui/button"
|
|
import { Skeleton } from "@/components/ui/skeleton"
|
|
import { MessageSquare, MessageCircle, ArrowRight, Clock } from "lucide-react"
|
|
import { clientFetch, getCookie } from "@/lib/api"
|
|
import { Avatar, AvatarFallback } from "@/components/ui/avatar"
|
|
import Link from "next/link"
|
|
import { RelativeTime } from "@/components/ui/relative-time"
|
|
|
|
interface PendingChatsWidgetProps {
|
|
settings?: {
|
|
showPreview?: boolean
|
|
}
|
|
}
|
|
|
|
interface Chat {
|
|
id: string
|
|
buyerId: string
|
|
telegramUsername?: string
|
|
lastUpdated: string
|
|
unreadCount: number
|
|
}
|
|
|
|
export default function PendingChatsWidget({ settings }: PendingChatsWidgetProps) {
|
|
const showPreview = settings?.showPreview !== false
|
|
const [chats, setChats] = useState<Chat[]>([])
|
|
const [isLoading, setIsLoading] = useState(true)
|
|
const [error, setError] = useState<string | null>(null)
|
|
|
|
const getVendorIdFromToken = () => {
|
|
const authToken = getCookie("Authorization") || ""
|
|
if (!authToken) return null
|
|
try {
|
|
const payload = JSON.parse(atob(authToken.split(".")[1]))
|
|
return payload.id
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
const fetchChats = async () => {
|
|
try {
|
|
setIsLoading(true)
|
|
setError(null)
|
|
const vendorId = getVendorIdFromToken()
|
|
if (!vendorId) {
|
|
setError("Please login to view chats")
|
|
return
|
|
}
|
|
|
|
const response = await clientFetch(`/chats/vendor/${vendorId}/batch?page=1&limit=5`)
|
|
|
|
const chatCounts = response.unreadCounts?.chatCounts || {}
|
|
const pendingChats = (response.chats || [])
|
|
.filter((c: any) => chatCounts[c._id] > 0)
|
|
.map((c: any) => ({
|
|
id: c._id,
|
|
buyerId: c.buyerId,
|
|
telegramUsername: c.telegramUsername,
|
|
lastUpdated: c.lastUpdated,
|
|
unreadCount: chatCounts[c._id] || 0
|
|
}))
|
|
|
|
setChats(pendingChats)
|
|
} catch (err) {
|
|
console.error("Error fetching chats:", err)
|
|
setError("Failed to load chats")
|
|
} finally {
|
|
setIsLoading(false)
|
|
}
|
|
}
|
|
|
|
useEffect(() => {
|
|
fetchChats()
|
|
}, [])
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<Card className="border-border/40 bg-background/50 backdrop-blur-sm">
|
|
<CardHeader className="pb-2">
|
|
<Skeleton className="h-5 w-32 mb-1" />
|
|
<Skeleton className="h-4 w-48" />
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
{[1, 2].map((i) => (
|
|
<div key={i} className="flex items-center gap-4">
|
|
<Skeleton className="h-10 w-10 rounded-full" />
|
|
<div className="space-y-2 flex-1">
|
|
<Skeleton className="h-4 w-3/4" />
|
|
<Skeleton className="h-3 w-1/4" />
|
|
</div>
|
|
</div>
|
|
))}
|
|
</CardContent>
|
|
</Card>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<Card className="border-border/40 bg-background/50 backdrop-blur-sm overflow-hidden flex flex-col h-full">
|
|
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
|
<div className="space-y-1">
|
|
<CardTitle className="flex items-center gap-2">
|
|
<MessageSquare className="h-5 w-5 text-emerald-500" />
|
|
Pending Chats
|
|
</CardTitle>
|
|
<CardDescription>
|
|
Unanswered customer messages
|
|
</CardDescription>
|
|
</div>
|
|
<Link href="/dashboard/chats">
|
|
<Button variant="ghost" size="sm" className="h-8 gap-1.5 text-xs">
|
|
Inbox
|
|
<ArrowRight className="h-3 w-3" />
|
|
</Button>
|
|
</Link>
|
|
</CardHeader>
|
|
<CardContent className="pt-4 flex-grow">
|
|
{error ? (
|
|
<div className="flex flex-col items-center justify-center py-10 text-center">
|
|
<MessageCircle className="h-10 w-10 text-muted-foreground/20 mb-3" />
|
|
<p className="text-sm text-muted-foreground">{error}</p>
|
|
</div>
|
|
) : chats.length === 0 ? (
|
|
<div className="flex flex-col items-center justify-center py-12 text-center">
|
|
<div className="h-12 w-12 rounded-full bg-emerald-500/10 flex items-center justify-center mb-4">
|
|
<MessageCircle className="h-6 w-6 text-emerald-500" />
|
|
</div>
|
|
<h3 className="font-medium">All caught up!</h3>
|
|
<p className="text-sm text-muted-foreground mt-1 max-w-xs">
|
|
No pending customer chats that require your attention.
|
|
</p>
|
|
</div>
|
|
) : (
|
|
<div className="space-y-1">
|
|
{chats.map((chat) => (
|
|
<Link
|
|
key={chat.id}
|
|
href={`/dashboard/chats/${chat.id}`}
|
|
className="flex items-center gap-4 p-3 rounded-xl hover:bg-muted/50 transition-colors group"
|
|
>
|
|
<div className="relative">
|
|
<Avatar className="h-10 w-10 border shadow-sm group-hover:scale-105 transition-transform">
|
|
<AvatarFallback className="bg-emerald-500/10 text-emerald-600 text-xs font-bold">
|
|
{(chat.telegramUsername || chat.buyerId).slice(0, 2).toUpperCase()}
|
|
</AvatarFallback>
|
|
</Avatar>
|
|
<span className="absolute -top-0.5 -right-0.5 h-3 w-3 bg-emerald-500 rounded-full ring-2 ring-background border border-background shadow-sm" />
|
|
</div>
|
|
<div className="flex-grow min-w-0">
|
|
<h4 className="font-semibold text-sm truncate group-hover:text-primary transition-colors">
|
|
{chat.telegramUsername ? `@${chat.telegramUsername}` : `Customer ${chat.buyerId.slice(-6)}`}
|
|
</h4>
|
|
<div className="flex items-center gap-2 mt-0.5 text-xs text-muted-foreground">
|
|
<Clock className="h-3 w-3" />
|
|
<RelativeTime date={new Date(chat.lastUpdated)} />
|
|
</div>
|
|
</div>
|
|
<div className="bg-emerald-500 text-emerald-foreground text-[10px] font-bold px-1.5 py-0.5 rounded-full shadow-sm ring-2 ring-emerald-500/10">
|
|
{chat.unreadCount}
|
|
</div>
|
|
</Link>
|
|
))}
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
)
|
|
}
|