diff --git a/app/dashboard/categories/page.tsx b/app/dashboard/categories/page.tsx index 0bf0216..f898aaf 100644 --- a/app/dashboard/categories/page.tsx +++ b/app/dashboard/categories/page.tsx @@ -25,7 +25,8 @@ import { } from "@/components/ui/alert-dialog"; import { apiRequest } from "@/lib/api"; import type { Category } from "@/models/categories"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; +import { motion, AnimatePresence } from "framer-motion"; // Drag and Drop imports import { DndProvider, useDrag, useDrop, DropTargetMonitor } from 'react-dnd'; @@ -49,14 +50,15 @@ export default function CategoriesPage() { const [editingCategory, setEditingCategory] = useState(null); const [categoryToDelete, setCategoryToDelete] = useState(null); const [expanded, setExpanded] = useState>({}); + const [loading, setLoading] = useState(true); // Get root categories sorted by order const rootCategories = categories .filter(cat => !cat.parentId) .sort((a, b) => (a.order || 0) - (b.order || 0)); - + // Get subcategories sorted by order - const getSubcategories = (parentId: string) => + const getSubcategories = (parentId: string) => categories .filter(cat => cat.parentId === parentId) .sort((a, b) => (a.order || 0) - (b.order || 0)); @@ -67,10 +69,13 @@ export default function CategoriesPage() { const fetchCategories = async () => { try { + setLoading(true); const fetchedCategories = await apiRequest("/categories", "GET"); setCategories(fetchedCategories); } catch (error) { toast.error("Failed to fetch categories"); + } finally { + setLoading(false); } }; @@ -82,13 +87,13 @@ export default function CategoriesPage() { try { // Find the highest order in the selected parent group - const siblings = selectedParentId + const siblings = selectedParentId ? categories.filter(cat => cat.parentId === selectedParentId) : categories.filter(cat => !cat.parentId); - - const maxOrder = siblings.reduce((max, cat) => + + const maxOrder = siblings.reduce((max, cat) => Math.max(max, cat.order || 0), 0); - + const response = await apiRequest("/categories", "POST", { name: newCategoryName, parentId: selectedParentId || undefined, @@ -110,7 +115,7 @@ export default function CategoriesPage() { name: newName, }); - setCategories(categories.map(cat => + setCategories(categories.map(cat => cat._id === categoryId ? { ...cat, name: newName } : cat )); setEditingCategory(null); @@ -138,55 +143,55 @@ export default function CategoriesPage() { // Create new array with updated orders const dragIndex = categories.findIndex(cat => cat._id === dragId); const hoverIndex = categories.findIndex(cat => cat._id === hoverId); - + if (dragIndex === -1 || hoverIndex === -1) return; - + const draggedCategory = categories[dragIndex]; - + // Make sure we're only reordering within the same parent group if (draggedCategory.parentId !== parentId) return; - + // Create a copy for reordering const updatedCategories = [...categories]; - + // Get only categories with the same parent for reordering const siblingCategories = updatedCategories .filter(cat => cat.parentId === parentId) .sort((a, b) => (a.order || 0) - (b.order || 0)); - + // Remove the dragged category from its position const draggedItem = siblingCategories.find(cat => cat._id === dragId); if (!draggedItem) return; - + const filteredSiblings = siblingCategories.filter(cat => cat._id !== dragId); - + // Find where to insert the dragged item const hoverItem = siblingCategories.find(cat => cat._id === hoverId); if (!hoverItem) return; - + const hoverPos = filteredSiblings.findIndex(cat => cat._id === hoverId); - + // Insert the dragged item at the new position filteredSiblings.splice(hoverPos, 0, draggedItem); - + // Update the order for all siblings filteredSiblings.forEach((cat, index) => { cat.order = index; }); - + // Update the categories state setCategories(updatedCategories.map(cat => { const updatedCat = filteredSiblings.find(c => c._id === cat._id); return updatedCat || cat; })); - + // Save the new order to the server using bulk update try { const categoriesToUpdate = filteredSiblings.map(cat => ({ _id: cat._id, order: cat.order })); - + await apiRequest("/categories/bulk-order", "PUT", { categories: categoriesToUpdate }); @@ -210,9 +215,9 @@ export default function CategoriesPage() { const hasSubcategories = subcategories.length > 0; const isEditing = editingCategory?._id === category._id; const isExpanded = expanded[category._id]; - + const ref = useRef(null); - + // Set up drag const [{ isDragging }, drag] = useDrag({ type: ItemTypes.CATEGORY, @@ -221,7 +226,7 @@ export default function CategoriesPage() { isDragging: monitor.isDragging(), }), }); - + // Set up drop const [{ handlerId, isOver }, drop] = useDrop({ accept: ItemTypes.CATEGORY, @@ -231,56 +236,64 @@ export default function CategoriesPage() { }), hover(item: DragItem, monitor) { if (!ref.current) return; - + const dragId = item.id; const hoverId = category._id; - + // Don't replace items with themselves if (dragId === hoverId) return; - + // Only allow reordering within the same parent if (item.parentId !== category.parentId) return; - + moveCategory(dragId, hoverId, category.parentId); }, }); - + // Connect the drag and drop refs drag(drop(ref)); - + return ( -
-
+
-
+
- - {hasSubcategories && ( - + ) : ( +
// Spacer )} - +
{isEditing ? ( setEditingCategory(prev => prev ? { ...prev, name: e.target.value } : prev)} - className="h-8 max-w-[200px]" + className="h-8 max-w-[200px] border-primary/30 focus-visible:ring-primary/20" autoFocus onKeyDown={(e) => { if (e.key === 'Enter' && editingCategory) { @@ -300,7 +313,7 @@ export default function CategoriesPage() { )}
- {isExpanded && subcategories.map(subcat => - - )} -
+ + {isExpanded && hasSubcategories && ( + + {subcategories.map(subcat => + + )} + + )} + + ); }; return ( -
+
-

- - Categories -

+
+

+ + Categories +

+

+ Manage your product categories and hierarchy +

+
-
- {/* Add Category Card - Takes up 2 columns */} - - - Add New Category +
+ {/* Add Category Card */} + + + + + Add New Category + + + Create a new category or subcategory + - +
-
-
- @@ -401,47 +436,59 @@ export default function CategoriesPage() { - {/* Category List Card - Takes up 3 columns */} - - - Category List + {/* Category List Card */} + + + Structure + + Drag and drop to reorder categories + - + -
- {rootCategories.length === 0 ? ( -

- No categories yet. Add your first category above. -

+
+ {loading ? ( +
+ +

Loading categories...

+
+ ) : rootCategories.length === 0 ? ( +
+ +

No categories yet

+

Add your first category to get started

+
) : ( - rootCategories.map(category => ( - - )) +
+ {rootCategories.map(category => ( + + ))} +
)}
- + {/* Delete Confirmation Dialog */} - {categoryToDelete && ( - setCategoryToDelete(null)}> - - - Are you sure? - - This will permanently delete the category "{categoryToDelete.name}". - This action cannot be undone. - - - - Cancel - Delete - - - - )} + setCategoryToDelete(null)}> + + + Are you sure? + + This will permanently delete the category "{categoryToDelete?.name}". + This action cannot be undone. + + + + Cancel + + Delete Category + + + +
);