Add modular dashboard widgets and layout editor
Some checks failed
Build Frontend / build (push) Failing after 7s
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.
This commit is contained in:
154
components/dashboard/recent-customers-widget.tsx
Normal file
154
components/dashboard/recent-customers-widget.tsx
Normal file
@@ -0,0 +1,154 @@
|
||||
"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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user