Files
ember-market-frontend/components/dashboard/low-stock-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

168 lines
7.6 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 { AlertCircle, Package, ArrowRight, ShoppingCart } from "lucide-react"
import { clientFetch } from "@/lib/api"
import Image from "next/image"
import Link from "next/link"
interface LowStockWidgetProps {
settings?: {
threshold?: number
itemCount?: number
}
}
interface LowStockProduct {
id: string
name: string
currentStock: number
unitType: string
image?: string
}
export default function LowStockWidget({ settings }: LowStockWidgetProps) {
const threshold = settings?.threshold || 5
const itemCount = settings?.itemCount || 5
const [products, setProducts] = useState<LowStockProduct[]>([])
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const fetchLowStock = async () => {
try {
setIsLoading(true)
setError(null)
// Implementation: We'll use the product-performance API and filter locally
// or a dedicated stock-report API if available.
// For now, let's use the product-performance endpoint which has stock info.
const response = await clientFetch('/analytics/product-performance')
const lowStockProducts = response
.filter((p: any) => p.currentStock <= threshold)
.sort((a: any, b: any) => a.currentStock - b.currentStock)
.slice(0, itemCount)
.map((p: any) => ({
id: p.productId,
name: p.name,
currentStock: p.currentStock,
unitType: p.unitType,
image: p.image
}))
setProducts(lowStockProducts)
} catch (err) {
console.error("Error fetching low stock data:", err)
setError("Failed to load inventory data")
} finally {
setIsLoading(false)
}
}
useEffect(() => {
fetchLowStock()
}, [threshold, 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-12 w-12 rounded-lg" />
<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">
<AlertCircle className="h-5 w-5 text-amber-500" />
Low Stock Alerts
</CardTitle>
<CardDescription>
Inventory checks (Threshold: {threshold})
</CardDescription>
</div>
<Link href="/dashboard/stock">
<Button variant="ghost" size="sm" className="h-8 gap-1.5 text-xs">
Manage
<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">
<Package className="h-10 w-10 text-muted-foreground/20 mb-3" />
<p className="text-sm text-muted-foreground">{error}</p>
</div>
) : products.length === 0 ? (
<div className="flex flex-col items-center justify-center py-12 text-center">
<div className="h-12 w-12 rounded-full bg-green-500/10 flex items-center justify-center mb-4">
<Package className="h-6 w-6 text-green-500" />
</div>
<h3 className="font-medium">All systems go</h3>
<p className="text-sm text-muted-foreground mt-1 max-w-xs">
No products currently under your threshold of {threshold} units.
</p>
</div>
) : (
<div className="space-y-1">
{products.map((product) => (
<div
key={product.id}
className="flex items-center gap-4 p-3 rounded-xl hover:bg-muted/50 transition-colors group"
>
<div className="h-12 w-12 relative rounded-lg border bg-muted overflow-hidden flex-shrink-0">
{product.image ? (
<Image
src={`/api/products/${product.id}/image`}
alt={product.name}
fill
className="object-cover group-hover:scale-110 transition-transform"
/>
) : (
<div className="h-full w-full flex items-center justify-center">
<ShoppingCart className="h-5 w-5 text-muted-foreground/40" />
</div>
)}
</div>
<div className="flex-grow min-w-0">
<h4 className="font-semibold text-sm truncate">{product.name}</h4>
<div className="flex items-center gap-1.5 mt-0.5">
<span className="text-[10px] uppercase font-mono tracking-wider text-muted-foreground bg-muted-foreground/10 px-1.5 py-0.5 rounded">
ID: {product.id.slice(-6)}
</span>
</div>
</div>
<div className="text-right">
<div className={`text-sm font-bold ${product.currentStock === 0 ? 'text-destructive' : 'text-amber-500'}`}>
{product.currentStock} {product.unitType}
</div>
<div className="text-[10px] text-muted-foreground font-medium uppercase tracking-tighter">Remaining</div>
</div>
</div>
))}
</div>
)}
</CardContent>
</Card>
)
}