diff --git a/web/dashboard/messages/ar.json b/web/dashboard/messages/ar.json
index fd6e558..74ce33c 100644
--- a/web/dashboard/messages/ar.json
+++ b/web/dashboard/messages/ar.json
@@ -651,7 +651,24 @@
"media": "صورة وفيديو",
"tabCatalog": "الكتالوج",
"tabBranch": "إعدادات الفرع",
- "selectBranchForOverrides": "اختر فرعاً من الأعلى لإدارة قائمة الفرع."
+ "selectBranchForOverrides": "اختر فرعاً من الأعلى لإدارة قائمة الفرع.",
+ "allItems": "كل الأصناف",
+ "searchItemsPlaceholder": "ابحث عن أصناف…",
+ "itemCount": "{count} أصناف",
+ "noItemsInCategory": "لا أصناف في هذه الفئة بعد",
+ "noItemsMatchSearch": "لا أصناف تطابق بحثك",
+ "outOfStock": "نفد المخزون",
+ "newItem": "صنف جديد",
+ "newCategory": "فئة جديدة",
+ "editCategoryTitle": "تعديل الفئة",
+ "close": "إغلاق",
+ "saving": "جاري الحفظ…",
+ "model3d": "نموذج ثلاثي الأبعاد",
+ "nameEnOptional": "الاسم بالإنجليزية (اختياري)",
+ "addItemSuccess": "تمت إضافة الصنف",
+ "updateItemSuccess": "تم تحديث الصنف",
+ "addCategorySuccess": "تمت إضافة الفئة",
+ "updateCategorySuccess": "تم تحديث الفئة"
},
"branchMenu": {
"title": "قائمة الفرع",
diff --git a/web/dashboard/messages/en.json b/web/dashboard/messages/en.json
index 868f3ea..7b55f83 100644
--- a/web/dashboard/messages/en.json
+++ b/web/dashboard/messages/en.json
@@ -668,7 +668,24 @@
"media": "Image & video",
"tabCatalog": "Catalog",
"tabBranch": "Branch settings",
- "selectBranchForOverrides": "Select a branch above to manage its menu overrides."
+ "selectBranchForOverrides": "Select a branch above to manage its menu overrides.",
+ "allItems": "All items",
+ "searchItemsPlaceholder": "Search items…",
+ "itemCount": "{count} items",
+ "noItemsInCategory": "No items in this category yet",
+ "noItemsMatchSearch": "No items match your search",
+ "outOfStock": "Out of stock",
+ "newItem": "New item",
+ "newCategory": "New category",
+ "editCategoryTitle": "Edit category",
+ "close": "Close",
+ "saving": "Saving…",
+ "model3d": "3D model",
+ "nameEnOptional": "English name (optional)",
+ "addItemSuccess": "Item added",
+ "updateItemSuccess": "Item updated",
+ "addCategorySuccess": "Category added",
+ "updateCategorySuccess": "Category updated"
},
"branchMenu": {
"title": "Branch Menu",
diff --git a/web/dashboard/messages/fa.json b/web/dashboard/messages/fa.json
index 056f493..a4d2f08 100644
--- a/web/dashboard/messages/fa.json
+++ b/web/dashboard/messages/fa.json
@@ -668,7 +668,24 @@
"media": "تصویر و ویدیو",
"tabCatalog": "کاتالوگ",
"tabBranch": "تنظیمات شعبه",
- "selectBranchForOverrides": "برای تنظیم منوی شعبه، یک شعبه از بالا انتخاب کنید."
+ "selectBranchForOverrides": "برای تنظیم منوی شعبه، یک شعبه از بالا انتخاب کنید.",
+ "allItems": "همه آیتمها",
+ "searchItemsPlaceholder": "جستجوی آیتمها…",
+ "itemCount": "{count} آیتم",
+ "noItemsInCategory": "هنوز آیتمی در این دسته نیست",
+ "noItemsMatchSearch": "آیتمی با این عبارت یافت نشد",
+ "outOfStock": "ناموجود",
+ "newItem": "آیتم جدید",
+ "newCategory": "دسته جدید",
+ "editCategoryTitle": "ویرایش دسته",
+ "close": "بستن",
+ "saving": "در حال ذخیره…",
+ "model3d": "مدل سهبعدی",
+ "nameEnOptional": "نام انگلیسی (اختیاری)",
+ "addItemSuccess": "آیتم اضافه شد",
+ "updateItemSuccess": "آیتم بهروز شد",
+ "addCategorySuccess": "دسته اضافه شد",
+ "updateCategorySuccess": "دسته بهروز شد"
},
"branchMenu": {
"title": "منوی شعبه",
diff --git a/web/dashboard/src/components/menu/menu-admin-screen.tsx b/web/dashboard/src/components/menu/menu-admin-screen.tsx
index 7aed279..dace65f 100644
--- a/web/dashboard/src/components/menu/menu-admin-screen.tsx
+++ b/web/dashboard/src/components/menu/menu-admin-screen.tsx
@@ -4,7 +4,7 @@ import { useMemo, useState } from "react";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { useLocale, useTranslations } from "next-intl";
import { useIsRtl } from "@/lib/use-is-rtl";
-import { Box, Pencil, Video } from "lucide-react";
+import { Box, Pencil, Plus, Search, Video, X } from "lucide-react";
import { Menu3dUpload } from "@/components/media/menu-3d-upload";
import { MenuAi3dGenerate } from "@/components/media/menu-ai-3d-generate";
import { CategoryVisual } from "@/components/menu/category-visual";
@@ -13,18 +13,22 @@ import type { CategoryIconSelection } from "@/components/menu/category-preset-pi
import { DEFAULT_CATEGORY_ICON_STYLE } from "@/lib/category-icon-presets";
import { apiGet, apiPatch, apiPost } from "@/lib/api/client";
import { useAuthStore } from "@/lib/stores/auth.store";
+import { useBranchStore } from "@/lib/stores/branch.store";
import { formatCurrency, formatNumber } from "@/lib/format";
import { PageHeader } from "@/components/layout/page-header";
import { MediaPairUpload } from "@/components/media/media-pair-upload";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { LabeledField } from "@/components/ui/labeled-field";
-import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
-import { Badge } from "@/components/ui/badge";
+import { Skeleton } from "@/components/ui/skeleton";
import { cn } from "@/lib/utils";
import { MenuItemLabels } from "@/components/menu/menu-item-labels";
import { MenuItemMedia } from "@/components/menu/menu-item-media";
import { buildCategoryNameMap, inferMenuItemKind } from "@/lib/menu-item-image";
+import { menuItemMatchesSearch } from "@/lib/menu-display";
+import { BranchMenuOverrides } from "@/components/menu/branch-menu-overrides";
+
+// ─── Types ────────────────────────────────────────────────────────────────────
interface MenuCategory {
id: string;
@@ -54,6 +58,26 @@ interface MenuItem {
isAvailable: boolean;
}
+interface ItemForm {
+ categoryId: string;
+ name: string;
+ nameEn: string;
+ price: string;
+ discount: string;
+ imageUrl: string;
+ videoUrl: string;
+ model3dUrl: string;
+}
+
+interface CatForm {
+ name: string;
+ icon: string;
+ iconPreset: CategoryIconSelection;
+ imageUrl: string;
+}
+
+// ─── Helpers ──────────────────────────────────────────────────────────────────
+
function discountedPrice(price: number, percent: number) {
if (percent <= 0) return price;
return Math.round(price * (1 - percent / 100));
@@ -63,6 +87,98 @@ function mediaField(url: string) {
return url.trim() === "" ? "" : url;
}
+const defaultItemForm: ItemForm = {
+ categoryId: "",
+ name: "",
+ nameEn: "",
+ price: "",
+ discount: "0",
+ imageUrl: "",
+ videoUrl: "",
+ model3dUrl: "",
+};
+
+const defaultCatForm: CatForm = {
+ name: "",
+ icon: "",
+ iconPreset: { iconPresetId: null, iconStyle: DEFAULT_CATEGORY_ICON_STYLE },
+ imageUrl: "",
+};
+
+// ─── Toggle Switch ────────────────────────────────────────────────────────────
+
+function ToggleSwitch({
+ checked,
+ onChange,
+ disabled,
+ label,
+}: {
+ checked: boolean;
+ onChange: (v: boolean) => void;
+ disabled?: boolean;
+ label?: string;
+}) {
+ return (
+
+ );
+}
+
+// ─── Modal wrapper ────────────────────────────────────────────────────────────
+
+function Modal({
+ open,
+ onClose,
+ title,
+ children,
+ maxWidth = "max-w-lg",
+}: {
+ open: boolean;
+ onClose: () => void;
+ title: string;
+ children: React.ReactNode;
+ maxWidth?: string;
+}) {
+ if (!open) return null;
+ return (
+
+
+
+
{title}
+
+
+
{children}
+
+
+ );
+}
+
+// ─── Main Component ───────────────────────────────────────────────────────────
+
export function MenuAdminScreen() {
const t = useTranslations("menuAdmin");
const tCommon = useTranslations("common");
@@ -70,41 +186,25 @@ export function MenuAdminScreen() {
const locale = useLocale();
const numberLocale = locale === "en" ? "en-US" : "fa-IR";
const cafeId = useAuthStore((s) => s.user?.cafeId);
+ const branchId = useBranchStore((s) => s.branchId);
const queryClient = useQueryClient();
- const [editingId, setEditingId] = useState(null);
- const [editingCategoryId, setEditingCategoryId] = useState(null);
- const [catName, setCatName] = useState("");
- const [catIcon, setCatIcon] = useState("");
- const [catIconPreset, setCatIconPreset] = useState({
- iconPresetId: null,
- iconStyle: DEFAULT_CATEGORY_ICON_STYLE,
- });
- const [catImageUrl, setCatImageUrl] = useState("");
- const [editCatName, setEditCatName] = useState("");
- const [editCatIcon, setEditCatIcon] = useState("");
- const [editCatIconPreset, setEditCatIconPreset] = useState({
- iconPresetId: null,
- iconStyle: DEFAULT_CATEGORY_ICON_STYLE,
- });
- const [editCatImageUrl, setEditCatImageUrl] = useState("");
- const [itemName, setItemName] = useState("");
- const [itemNameEn, setItemNameEn] = useState("");
- const [itemPrice, setItemPrice] = useState("");
- const [itemDiscount, setItemDiscount] = useState("0");
- const [itemCategoryId, setItemCategoryId] = useState("");
- const [itemImageUrl, setItemImageUrl] = useState("");
- const [itemVideoUrl, setItemVideoUrl] = useState("");
- const [itemModel3dUrl, setItemModel3dUrl] = useState("");
+ // ── UI state ───────────────────────────────────────────────────────────────
+ const [activeTab, setActiveTab] = useState<"catalog" | "branch">("catalog");
+ const [selectedCategoryId, setSelectedCategoryId] = useState("all");
+ const [itemSearch, setItemSearch] = useState("");
- const [editName, setEditName] = useState("");
- const [editNameEn, setEditNameEn] = useState("");
- const [editPrice, setEditPrice] = useState("");
- const [editDiscount, setEditDiscount] = useState("0");
- const [editImageUrl, setEditImageUrl] = useState("");
- const [editVideoUrl, setEditVideoUrl] = useState("");
- const [editModel3dUrl, setEditModel3dUrl] = useState("");
+ // Item modal
+ const [itemModalOpen, setItemModalOpen] = useState(false);
+ const [editingItem, setEditingItem] = useState