Files
ember-market-frontend/components/dashboard/quick-actions.tsx
g e9737c8b24
All checks were successful
Build Frontend / build (push) Successful in 1m11s
Refactor UI to remove Christmas theme and improve actions
Removed all Christmas-specific theming and logic from the home page and navbar, standardizing colors to indigo. Updated QuickActions to open a modal for adding products instead of navigating to a new page, including logic for product creation and category fetching. Simplified ChatTable row animations and fixed minor layout issues. Updated button styles and mobile menu links for consistency.
2026-01-12 07:43:33 +00:00

217 lines
7.6 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="hover:border-primary/50 transition-colors cursor-pointer group h-full border-border/40 bg-background/50 backdrop-blur-sm">
<CardContent className="p-6 flex flex-col items-center text-center">
<div className={`p-3 rounded-xl ${action.color} mb-4 group-hover:scale-110 transition-transform`}>
<action.icon className="h-6 w-6" />
</div>
<h3 className="font-semibold text-lg">{action.title}</h3>
<p className="text-sm text-muted-foreground 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={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
>
{isModalAction ? (
<div onClick={() => setModalOpen(true)}>
<CardContentWrapper />
</div>
) : (
<Link href={action.href}>
<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}
/>
</>
)
}