Files
ember-market-frontend/components/dashboard/pending-chats-widget.tsx
g 318927cd0c
Some checks failed
Build Frontend / build (push) Failing after 7s
Add modular dashboard widgets and layout editor
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.
2026-01-12 10:39:50 +00:00

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>
)
}