diff --git a/RestroHub-FrontEnd/src/components/admin/menu/Menus.jsx b/RestroHub-FrontEnd/src/components/admin/menu/Menus.jsx index 0da84c4e..3983c4cd 100644 --- a/RestroHub-FrontEnd/src/components/admin/menu/Menus.jsx +++ b/RestroHub-FrontEnd/src/components/admin/menu/Menus.jsx @@ -17,6 +17,7 @@ const Menus = () => { const [allCategories, setAllCategories] = useState([]); const menuGridRef = useRef(null); const menusGridRef = useRef(null); + const categorySidebarRef = useRef(null); // FOOD ITEM MODAL STATE const [isModalOpen, setIsModalOpen] = useState(false); @@ -24,6 +25,7 @@ const Menus = () => { // CATEGORY MODAL STATE const [isCategoryModalOpen, setIsCategoryModalOpen] = useState(false); + const [editingCategory, setEditingCategory] = useState(null); // MENU CREATION MODAL STATE const [isMenuModalOpen, setIsMenuModalOpen] = useState(false); @@ -47,6 +49,11 @@ const Menus = () => { } }; + const refreshFoodItemsAndCategoryCounts = () => { + menuGridRef.current?.refreshFoods(); + categorySidebarRef.current?.refreshCategoryCounts(); + }; + // FOOD ITEM MODAL HANDLERS const openAddModal = () => { setEditingItem(null); @@ -67,16 +74,37 @@ const Menus = () => { const closeModal = () => { setIsModalOpen(false); setEditingItem(null); - menuGridRef.current?.refreshFoods(); + }; + + const handleFoodItemSaved = () => { + closeModal(); + refreshFoodItemsAndCategoryCounts(); }; // CATEGORY MODAL HANDLERS const openCategoryModal = () => { + setEditingCategory(null); + setIsCategoryModalOpen(true); + }; + + const openEditCategoryModal = (category) => { + setEditingCategory(category); setIsCategoryModalOpen(true); }; const closeCategoryModal = () => { setIsCategoryModalOpen(false); + setEditingCategory(null); + }; + + const handleCategorySaved = () => { + closeCategoryModal(); + categorySidebarRef.current?.refreshCategories(); + menuGridRef.current?.refreshFoods(); + }; + + const handleCategoryDeleted = () => { + menuGridRef.current?.refreshFoods(); }; // MENU CREATION MODAL HANDLERS @@ -148,15 +176,19 @@ const Menus = () => {
@@ -177,6 +209,7 @@ const Menus = () => { @@ -185,6 +218,8 @@ const Menus = () => { {/* Menu Creation Modal */} diff --git a/RestroHub-FrontEnd/src/components/admin/menu/menuCard/CategoryFormModal.jsx b/RestroHub-FrontEnd/src/components/admin/menu/menuCard/CategoryFormModal.jsx index e1effa26..1c9b3c44 100644 --- a/RestroHub-FrontEnd/src/components/admin/menu/menuCard/CategoryFormModal.jsx +++ b/RestroHub-FrontEnd/src/components/admin/menu/menuCard/CategoryFormModal.jsx @@ -1,148 +1,239 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; import { Dialog } from "@headlessui/react"; -import { X, Loader2, Tag, Type, FileText } from "lucide-react"; +import { AlertCircle, FileText, Loader2, Tag, Type, X } from "lucide-react"; +import toast from "react-hot-toast"; import api from "@services/common/api"; -const CategoryFormModal = ({ isOpen, onClose }) => { - const [formData, setFormData] = useState({ - name: "", - description: "" - }); +const getErrorMessage = (err, fallback) => + err.response?.data?.message || err.response?.data?.error || err.message || fallback; +const isValidCategoryId = (categoryId) => { + const normalizedId = String(categoryId ?? '').trim(); + const isNumericId = /^\d+$/.test(normalizedId); + const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(normalizedId); + + return isNumericId || isUuid; +}; + +const getSafeCategoryId = (categoryId) => { + const normalizedId = String(categoryId ?? '').trim(); + + if (!isValidCategoryId(normalizedId)) { + throw new Error('Invalid category selected'); + } + + return encodeURIComponent(normalizedId); +}; + +const initialFormData = { + name: "", + description: "" +}; + +const CategoryFormModal = ({ isOpen, onClose, editingCategory, onSaved }) => { + const [formData, setFormData] = useState(initialFormData); + const [errors, setErrors] = useState({}); + const [submitError, setSubmitError] = useState(""); const [submitting, setSubmitting] = useState(false); + const isEditing = Boolean(editingCategory?.categoryId); + + useEffect(() => { + if (!isOpen) return; + + setFormData({ + name: editingCategory?.name || "", + description: editingCategory?.description || "" + }); + setErrors({}); + setSubmitError(""); + }, [editingCategory, isOpen]); + const updateField = (field, value) => { setFormData(prev => ({ ...prev, [field]: value })); + setErrors(prev => ({ ...prev, [field]: undefined })); + }; + + const validateForm = () => { + const nextErrors = {}; + const name = formData.name.trim(); + const description = formData.description.trim(); + + if (!name) nextErrors.name = "Category name is required."; + if (name && name.length < 2) nextErrors.name = "Category name must be at least 2 characters."; + if (name.length > 50) nextErrors.name = "Category name must be 50 characters or less."; + if (description.length > 255) nextErrors.description = "Description must be 255 characters or less."; + + setErrors(nextErrors); + return Object.keys(nextErrors).length === 0; + }; + + const handleClose = () => { + if (submitting) return; + onClose(); }; const handleSubmit = async (e) => { e.preventDefault(); + setSubmitError(""); + + if (!validateForm()) return; + try { setSubmitting(true); const payload = { - name: formData.name, - description: formData.description, + name: formData.name.trim(), + description: formData.description.trim(), isDelete: false }; - await api.post("/secure/api/v1/categories/addCategory", payload); - - onClose(); - setFormData({ name: "", description: "" }); - + if (isEditing) { + const safeCategoryId = getSafeCategoryId(editingCategory.categoryId); + await api.put(`/secure/api/v1/categories/update/${safeCategoryId}`, payload); + toast.success("Category updated successfully"); + } else { + await api.post("/secure/api/v1/categories/addCategory", payload); + toast.success("Category created successfully"); + } + + setFormData(initialFormData); + onSaved(); } catch (err) { - console.error("Category create failed:", err.response?.data || err); + console.error("Category save failed:", err.response?.data || err); + const message = getErrorMessage(err, "Failed to save category"); + setSubmitError(message); + toast.error(message); } finally { setSubmitting(false); } }; return ( - - {/* Backdrop */} -