From 639573dfdefe3099383e8a664a0cacbe6c88e5e8 Mon Sep 17 00:00:00 2001 From: "soroush.asadi" Date: Sat, 30 May 2026 00:28:56 +0330 Subject: [PATCH] Add dashboard chrome to POS and collapsible sidebar Wrap the POS terminal in the sidebar + topbar layout via a nested fullscreen layout, and make the sidebar collapse to an icon-only rail with a persisted toggle so operators keep navigation on the POS screen. Co-Authored-By: Claude Sonnet 4.6 --- web/dashboard/messages/ar.json | 25 ++- web/dashboard/messages/en.json | 25 ++- web/dashboard/messages/fa.json | 25 ++- .../app/[locale]/(fullscreen)/pos/layout.tsx | 50 +++++ .../app/[locale]/(fullscreen)/pos/page.tsx | 13 +- .../src/components/layout/sidebar.tsx | 188 ++++++++++++++---- .../src/components/pos/pos-screen.tsx | 12 -- 7 files changed, 278 insertions(+), 60 deletions(-) create mode 100644 web/dashboard/src/app/[locale]/(fullscreen)/pos/layout.tsx diff --git a/web/dashboard/messages/ar.json b/web/dashboard/messages/ar.json index b346722..ea33950 100644 --- a/web/dashboard/messages/ar.json +++ b/web/dashboard/messages/ar.json @@ -58,6 +58,8 @@ }, "nav": { "aria": "القائمة الرئيسية", + "collapseSidebar": "طي الشريط الجانبي", + "expandSidebar": "توسيع الشريط الجانبي", "groups": { "operations": "العمليات اليومية", "menuSales": "القائمة والمبيعات", @@ -945,7 +947,28 @@ "featureDiscover": "ملف الاكتشاف (ذكاء اصطناعي)", "featureOn": "مفعّل", "featureOff": "غير متاح — ترقية", - "featureMenu3dUpgrade": "القائمة 3D متاحة في برو وما فوق." + "featureMenu3dUpgrade": "القائمة 3D متاحة في برو وما فوق.", + "checkout": { + "title": "الفاتورة والدفع", + "subtitle": "راجع طلبك وادفع", + "backToPlans": "العودة إلى الخطط", + "invalidPlan": "الخطة المحددة غير متاحة للشراء عبر الإنترنت.", + "invoiceLabel": "فاتورة مبدئية", + "invoiceNo": "رقم الفاتورة", + "issuedAt": "تاريخ الإصدار", + "billingPeriod": "مدة الاشتراك", + "monthsCount": "{count} شهر", + "description": "الوصف", + "qty": "الكمية", + "unitPrice": "سعر الوحدة", + "amount": "المبلغ", + "planLine": "اشتراك خطة {plan}", + "subtotal": "المجموع الفرعي", + "total": "المبلغ المستحق", + "secureNote": "تتم المعالجة عبر بوابة دفع بنكية آمنة.", + "payTotal": "ادفع {total}", + "redirecting": "جارٍ التحويل إلى البوابة..." + } }, "settings": { "title": "الإعدادات", diff --git a/web/dashboard/messages/en.json b/web/dashboard/messages/en.json index 4d3fa7c..539dd30 100644 --- a/web/dashboard/messages/en.json +++ b/web/dashboard/messages/en.json @@ -69,6 +69,8 @@ }, "nav": { "aria": "Main navigation", + "collapseSidebar": "Collapse sidebar", + "expandSidebar": "Expand sidebar", "groups": { "operations": "Daily operations", "menuSales": "Menu & sales", @@ -1017,7 +1019,28 @@ "featureOff": "Not included — upgrade", "featureMenu3dUpgrade": "3D menu is available on Pro and higher plans.", "featureMenuAi3d": "AI 3D generation", - "featureMenuAi3dUpgrade": "AI 3D generation is on Business and Enterprise (100 per month)." + "featureMenuAi3dUpgrade": "AI 3D generation is on Business and Enterprise (100 per month).", + "checkout": { + "title": "Invoice & Payment", + "subtitle": "Review your order and pay", + "backToPlans": "Back to plans", + "invalidPlan": "The selected plan is not available for online purchase.", + "invoiceLabel": "Proforma invoice", + "invoiceNo": "Invoice no.", + "issuedAt": "Issued", + "billingPeriod": "Billing period", + "monthsCount": "{count} mo", + "description": "Description", + "qty": "Qty", + "unitPrice": "Unit price", + "amount": "Amount", + "planLine": "{plan} plan subscription", + "subtotal": "Subtotal", + "total": "Amount due", + "secureNote": "Payment is processed through a secure bank gateway.", + "payTotal": "Pay {total}", + "redirecting": "Redirecting to gateway..." + } }, "settings": { "title": "Settings", diff --git a/web/dashboard/messages/fa.json b/web/dashboard/messages/fa.json index c4a4974..94b33c0 100644 --- a/web/dashboard/messages/fa.json +++ b/web/dashboard/messages/fa.json @@ -69,6 +69,8 @@ }, "nav": { "aria": "منوی اصلی", + "collapseSidebar": "جمع کردن نوار کناری", + "expandSidebar": "باز کردن نوار کناری", "groups": { "operations": "عملیات روزانه", "menuSales": "منو و فروش", @@ -1018,7 +1020,28 @@ "featureOff": "غیرفعال — ارتقا دهید", "featureMenu3dUpgrade": "منوی ۳D در پلن حرفه‌ای و بالاتر فعال است.", "featureMenuAi3d": "تولید ۳D با AI", - "featureMenuAi3dUpgrade": "تولید ۳D با هوش مصنوعی در پلن کسب‌وکار و سازمانی (۱۰۰ بار در ماه) فعال است." + "featureMenuAi3dUpgrade": "تولید ۳D با هوش مصنوعی در پلن کسب‌وکار و سازمانی (۱۰۰ بار در ماه) فعال است.", + "checkout": { + "title": "پیش‌فاکتور و پرداخت", + "subtitle": "جزئیات سفارش را بررسی و پرداخت کنید", + "backToPlans": "بازگشت به پلن‌ها", + "invalidPlan": "پلن انتخاب‌شده برای خرید آنلاین معتبر نیست.", + "invoiceLabel": "پیش‌فاکتور", + "invoiceNo": "شماره فاکتور", + "issuedAt": "تاریخ صدور", + "billingPeriod": "مدت اشتراک", + "monthsCount": "{count} ماه", + "description": "شرح", + "qty": "تعداد", + "unitPrice": "قیمت واحد", + "amount": "مبلغ", + "planLine": "اشتراک پلن {plan}", + "subtotal": "جمع جزء", + "total": "مبلغ قابل پرداخت", + "secureNote": "پرداخت از طریق درگاه امن بانکی انجام می‌شود.", + "payTotal": "پرداخت {total}", + "redirecting": "در حال انتقال به درگاه..." + } }, "settings": { "title": "تنظیمات", diff --git a/web/dashboard/src/app/[locale]/(fullscreen)/pos/layout.tsx b/web/dashboard/src/app/[locale]/(fullscreen)/pos/layout.tsx new file mode 100644 index 0000000..2fd3f63 --- /dev/null +++ b/web/dashboard/src/app/[locale]/(fullscreen)/pos/layout.tsx @@ -0,0 +1,50 @@ +"use client"; + +import { useLocale } from "next-intl"; +import { Sidebar } from "@/components/layout/sidebar"; +import { Topbar } from "@/components/layout/topbar"; +import { CafeThemeProvider } from "@/components/theme/cafe-theme-provider"; + +/** + * POS route layout — wraps the terminal in the standard dashboard chrome + * (collapsible sidebar + topbar) but keeps the main content area + * overflow-hidden so PosScreen can manage its own internal scrolling. + */ +export default function PosLayout({ + children, +}: { + children: React.ReactNode; +}) { + const locale = useLocale(); + const isRtl = locale !== "en"; + + const mainColumn = ( +
+ +
+ {children} +
+
+ ); + + return ( + +
+ {isRtl ? ( + <> + + {mainColumn} + + ) : ( + <> + + {mainColumn} + + )} +
+
+ ); +} diff --git a/web/dashboard/src/app/[locale]/(fullscreen)/pos/page.tsx b/web/dashboard/src/app/[locale]/(fullscreen)/pos/page.tsx index a0a76fc..c73a4a7 100644 --- a/web/dashboard/src/app/[locale]/(fullscreen)/pos/page.tsx +++ b/web/dashboard/src/app/[locale]/(fullscreen)/pos/page.tsx @@ -1,16 +1,11 @@ import { Suspense } from "react"; -import { CafeThemeProvider } from "@/components/theme/cafe-theme-provider"; import { PosScreen } from "@/components/pos/pos-screen"; -/** Full-viewport POS terminal — no sidebar, no topbar. */ +/** POS terminal — chrome (sidebar + topbar) is provided by layout.tsx */ export default function PosPage() { return ( - -
- - - -
-
+ + + ); } diff --git a/web/dashboard/src/components/layout/sidebar.tsx b/web/dashboard/src/components/layout/sidebar.tsx index 69f5b02..de88055 100644 --- a/web/dashboard/src/components/layout/sidebar.tsx +++ b/web/dashboard/src/components/layout/sidebar.tsx @@ -1,7 +1,7 @@ "use client"; import { useCallback, useEffect, useMemo, useState } from "react"; -import { ChevronDown } from "lucide-react"; +import { ChevronDown, ChevronLeft, ChevronRight } from "lucide-react"; import { useTranslations } from "next-intl"; import { Link, usePathname } from "@/i18n/routing"; import { canSeeNavGroup, canSeeNavItem } from "@/lib/auth-permissions"; @@ -18,6 +18,17 @@ import { cn } from "@/lib/utils"; type OpenGroupsState = Partial>; +const SIDEBAR_COLLAPSED_STORAGE_KEY = "meezi.sidebar.collapsed"; + +function readStoredCollapsed(): boolean { + if (typeof window === "undefined") return false; + try { + return localStorage.getItem(SIDEBAR_COLLAPSED_STORAGE_KEY) === "1"; + } catch { + return false; + } +} + function readStoredOpenGroups(): OpenGroupsState { if (typeof window === "undefined") return {}; try { @@ -50,17 +61,22 @@ function NavLink({ item, label, active, + collapsed, }: { item: NavItemDef; label: string; active: boolean; + collapsed: boolean; }) { const Icon = item.icon; return ( - {label} + {!collapsed && {label}} ); } @@ -86,6 +103,7 @@ function NavGroupSection({ role, branchId, tItem, + collapsed, }: { group: NavGroupDef; title: string; @@ -95,12 +113,35 @@ function NavGroupSection({ role: string | undefined; branchId: string | null | undefined; tItem: (key: string) => string; + collapsed: boolean; }) { const visibleItems = group.items.filter((item) => canSeeNavItem(item.key, role, branchId) ); if (visibleItems.length === 0) return null; + // Collapsed: drop the group header, show items as a flat icon-only list + // with a subtle divider between groups. + if (collapsed) { + return ( +
+ {visibleItems.map((item) => { + const active = + pathname === item.href || pathname.startsWith(`${item.href}/`); + return ( + + ); + })} +
+ ); + } + return (
+
); } diff --git a/web/dashboard/src/components/pos/pos-screen.tsx b/web/dashboard/src/components/pos/pos-screen.tsx index 75aa272..eaac0ed 100644 --- a/web/dashboard/src/components/pos/pos-screen.tsx +++ b/web/dashboard/src/components/pos/pos-screen.tsx @@ -7,7 +7,6 @@ import { useTranslations, useLocale } from "next-intl"; import { ChevronLeft, ChevronRight, - LayoutDashboard, Minus, Package, Plus, @@ -902,17 +901,6 @@ export function PosScreen() {
- - {/* Dashboard shortcut — only visible to Owner / Manager */} - {isManager && ( - - - {cafeName} - - )}
{/* ── Pay mode ──────────────────────────────────────────────────────── */}