All checks were successful
Build Frontend / build (push) Successful in 1m11s
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.
217 lines
7.6 KiB
TypeScript
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}
|
|
/>
|
|
</>
|
|
)
|
|
}
|