Files
ember-market-frontend/components/dashboard/quick-actions.tsx
g 6997838bf7
All checks were successful
Build Frontend / build (push) Successful in 1m11s
Revamp dashboard UI with improved dark theme styles
Updated category, quick actions, and product table components to use enhanced dark theme styling, including new background colors, borders, gradients, and shadow effects. Improved visual hierarchy, contrast, and hover states for better user experience and consistency across dashboard elements.
2026-01-12 08:19:59 +00:00

220 lines
8.2 KiB
TypeScript

"use client"
import { useState, useEffect, ChangeEvent } from "react"
import Link from "next/link"
import { motion } from "framer-motion"
import {
PlusCircle,
Truck,
BarChart3,
MessageSquare,
} from "lucide-react"
import { Card, CardContent } from "@/components/ui/card"
import dynamic from "next/dynamic"
import { Product } from "@/models/products"
import { Category } from "@/models/categories"
import { clientFetch } from "@/lib/api"
import { toast } from "sonner"
const ProductModal = dynamic(() => import("@/components/modals/product-modal").then(mod => ({ default: mod.ProductModal })), {
loading: () => null
});
const actions = [
{
title: "Add Product",
icon: PlusCircle,
href: "/dashboard/products/new", // Fallback text
color: "bg-blue-500/10 text-blue-500",
description: "Create a new listing",
action: "modal"
},
{
title: "Process Orders",
icon: Truck,
href: "/dashboard/orders?status=paid",
color: "bg-emerald-500/10 text-emerald-500",
description: "Ship pending orders"
},
{
title: "Analytics",
icon: BarChart3,
href: "/dashboard/analytics",
color: "bg-purple-500/10 text-purple-500",
description: "View sales performance"
},
{
title: "Messages",
icon: MessageSquare,
href: "/dashboard/chats",
color: "bg-amber-500/10 text-amber-500",
description: "Chat with customers"
}
]
export default function QuickActions() {
const [modalOpen, setModalOpen] = useState(false);
const [loading, setLoading] = useState(false);
const [categories, setCategories] = useState<Category[]>([]);
const [productData, setProductData] = useState<Product>({
name: "",
description: "",
unitType: "pcs",
category: "",
pricing: [{ minQuantity: 1, pricePerUnit: 0 }],
image: null,
costPerUnit: 0,
});
// Fetch categories on mount
useEffect(() => {
const fetchCategories = async () => {
try {
const data = await clientFetch('/categories');
setCategories(data);
} catch (error) {
console.error("Failed to fetch categories:", error);
}
};
fetchCategories();
}, []);
const handleChange = (e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
setProductData({ ...productData, [e.target.name]: e.target.value });
};
const handleTieredPricingChange = (e: ChangeEvent<HTMLInputElement>, index: number) => {
const updatedPricing = [...productData.pricing];
const name = e.target.name as "minQuantity" | "pricePerUnit";
updatedPricing[index][name] = e.target.valueAsNumber || 0;
setProductData({ ...productData, pricing: updatedPricing });
};
const handleAddTier = () => {
setProductData((prev) => ({
...prev,
pricing: [...prev.pricing, { minQuantity: 1, pricePerUnit: 0 }],
}));
};
const handleRemoveTier = (index: number) => {
setProductData((prev) => ({
...prev,
pricing: prev.pricing.filter((_, i) => i !== index),
}));
};
const handleSaveProduct = async (data: Product, file?: File | null) => {
try {
setLoading(true);
// Prepare the product data
const payload = {
...data,
stockTracking: data.stockTracking ?? true,
currentStock: data.currentStock ?? 0,
lowStockThreshold: data.lowStockThreshold ?? 10,
stockStatus: data.stockStatus ?? 'out_of_stock'
};
const productResponse = await clientFetch("/products", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
if (file) {
const formData = new FormData();
formData.append("file", file);
await fetch(`${process.env.NEXT_PUBLIC_API_URL}/products/${productResponse._id}/image`, {
method: "PUT",
headers: {
Authorization: `Bearer ${document.cookie.split("; ").find((row) => row.startsWith("Authorization="))?.split("=")[1]}`,
},
body: formData,
});
}
setModalOpen(false);
setProductData({
name: "",
description: "",
unitType: "pcs",
category: "",
pricing: [{ minQuantity: 1, pricePerUnit: 0 }],
image: null,
costPerUnit: 0,
});
toast.success("Product added successfully");
// Optional: trigger a refresh of products or stats if needed
// currently just closing modal
} catch (error) {
console.error(error);
toast.error("Failed to save product");
} finally {
setLoading(false);
}
};
return (
<>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
{actions.map((action, index) => {
const isModalAction = action.action === "modal";
const CardContentWrapper = () => (
<Card className="h-full border-none bg-black/40 backdrop-blur-xl hover:bg-black/60 transition-all duration-300 group overflow-hidden relative">
<div className="absolute inset-0 border border-white/10 rounded-xl pointer-events-none group-hover:border-white/20 transition-colors" />
<div className={`absolute inset-0 bg-gradient-to-br ${action.color.split(' ')[0].replace('/10', '/5')} opacity-0 group-hover:opacity-100 transition-opacity duration-500`} />
<CardContent className="p-6 flex flex-col items-center text-center relative z-10">
<div className={`p-4 rounded-2xl ${action.color.replace('bg-', 'bg-opacity-10 bg-')} mb-4 group-hover:scale-110 transition-transform duration-300 shadow-lg shadow-black/20`}>
<action.icon className="h-6 w-6" />
</div>
<h3 className="font-bold text-lg text-white group-hover:text-primary transition-colors">{action.title}</h3>
<p className="text-sm text-zinc-400 mt-1">{action.description}</p>
</CardContent>
</Card>
);
return (
<motion.div
key={action.title}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.1 }}
whileHover={{ y: -5 }}
whileTap={{ scale: 0.98 }}
>
{isModalAction ? (
<div onClick={() => setModalOpen(true)} className="cursor-pointer h-full">
<CardContentWrapper />
</div>
) : (
<Link href={action.href} className="h-full block">
<CardContentWrapper />
</Link>
)}
</motion.div>
);
})}
</div>
<ProductModal
open={modalOpen}
onClose={() => setModalOpen(false)}
onSave={handleSaveProduct}
productData={productData}
categories={categories}
editing={false}
handleChange={handleChange}
handleTieredPricingChange={handleTieredPricingChange}
handleAddTier={handleAddTier}
handleRemoveTier={handleRemoveTier}
setProductData={setProductData}
/>
</>
)
}