Files
ember-market-frontend/components/dashboard/recent-customers-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

155 lines
6.9 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 { Users, User, ArrowRight, DollarSign } from "lucide-react"
import { getCustomerInsightsWithStore, formatGBP } from "@/lib/api"
import { Avatar, AvatarFallback } from "@/components/ui/avatar"
import Link from "next/link"
interface RecentCustomersWidgetProps {
settings?: {
itemCount?: number
showSpent?: boolean
}
}
interface Customer {
id: string
name: string
username?: string
orderCount: number
totalSpent: number
}
export default function RecentCustomersWidget({ settings }: RecentCustomersWidgetProps) {
const itemCount = settings?.itemCount || 5
const showSpent = settings?.showSpent !== false
const [customers, setCustomers] = useState<Customer[]>([])
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const fetchCustomers = async () => {
try {
setIsLoading(true)
setError(null)
// The API returns topCustomers, but we'll use 'recent' sorting to show new engagement
const response = await getCustomerInsightsWithStore(1, itemCount, "recent")
const mappedCustomers = (response.topCustomers || []).map((c: any) => ({
id: c._id,
name: c.displayName || c.username || `Customer ${c._id.slice(-4)}`,
username: c.username,
orderCount: c.orderCount || 0,
totalSpent: c.totalSpent || 0
}))
setCustomers(mappedCustomers)
} catch (err) {
console.error("Error fetching customers:", err)
setError("Failed to load customer data")
} finally {
setIsLoading(false)
}
}
useEffect(() => {
fetchCustomers()
}, [itemCount])
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, 3].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">
<Users className="h-5 w-5 text-indigo-500" />
Recent Customers
</CardTitle>
<CardDescription>
Latest and newest connections
</CardDescription>
</div>
<Link href="/dashboard/customers">
<Button variant="ghost" size="sm" className="h-8 gap-1.5 text-xs">
View All
<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">
<User className="h-10 w-10 text-muted-foreground/20 mb-3" />
<p className="text-sm text-muted-foreground">{error}</p>
</div>
) : customers.length === 0 ? (
<div className="flex flex-col items-center justify-center py-12 text-center">
<div className="h-12 w-12 rounded-full bg-indigo-500/10 flex items-center justify-center mb-4">
<Users className="h-6 w-6 text-indigo-500" />
</div>
<h3 className="font-medium">No customers yet</h3>
<p className="text-sm text-muted-foreground mt-1 max-w-xs">
This widget will populate once people start browsing and buying.
</p>
</div>
) : (
<div className="space-y-1">
{customers.map((customer) => (
<div
key={customer.id}
className="flex items-center gap-4 p-3 rounded-xl hover:bg-muted/50 transition-colors group"
>
<Avatar className="h-10 w-10 border shadow-sm group-hover:scale-105 transition-transform">
<AvatarFallback className="bg-indigo-500/10 text-indigo-600 text-xs font-bold">
{customer.name.slice(0, 2).toUpperCase()}
</AvatarFallback>
</Avatar>
<div className="flex-grow min-w-0">
<h4 className="font-semibold text-sm truncate group-hover:text-primary transition-colors">{customer.name}</h4>
<div className="flex items-center gap-2 mt-0.5">
<span className="text-xs text-muted-foreground">
{customer.orderCount} order{customer.orderCount !== 1 ? 's' : ''}
</span>
</div>
</div>
{showSpent && (
<div className="text-right">
<div className="text-sm font-bold text-foreground">
{formatGBP(customer.totalSpent)}
</div>
<div className="text-[10px] text-muted-foreground font-medium uppercase tracking-tighter">Total Spent</div>
</div>
)}
</div>
))}
</div>
)}
</CardContent>
</Card>
)
}