feat: add admin menu configuration and sidebar improvements
- Add AdminMenuConfig model for per-platform menu customization - Add menu registry for centralized menu configuration - Add my-menu-config and platform-menu-config admin pages - Update sidebar with improved layout and Alpine.js interactions - Add FrontendType enum for admin/vendor menu separation - Document self-contained module patterns in session note Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
18
app/config/__init__.py
Normal file
18
app/config/__init__.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# app/config/__init__.py
|
||||
"""Configuration modules for the application."""
|
||||
|
||||
from .menu_registry import (
|
||||
ADMIN_MENU_REGISTRY,
|
||||
VENDOR_MENU_REGISTRY,
|
||||
AdminMenuItem,
|
||||
VendorMenuItem,
|
||||
get_all_menu_item_ids,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"ADMIN_MENU_REGISTRY",
|
||||
"VENDOR_MENU_REGISTRY",
|
||||
"AdminMenuItem",
|
||||
"VendorMenuItem",
|
||||
"get_all_menu_item_ids",
|
||||
]
|
||||
546
app/config/menu_registry.py
Normal file
546
app/config/menu_registry.py
Normal file
@@ -0,0 +1,546 @@
|
||||
# app/config/menu_registry.py
|
||||
"""
|
||||
Menu registry for Admin and Vendor frontends.
|
||||
|
||||
This module defines the complete menu structure for both frontends.
|
||||
Menu items are identified by unique IDs that are used for:
|
||||
- Storing visibility configuration in AdminMenuConfig
|
||||
- Checking access in require_menu_access() dependency
|
||||
- Rendering the sidebar dynamically
|
||||
|
||||
The registry is the single source of truth for menu structure.
|
||||
Database only stores visibility overrides (is_visible=False).
|
||||
"""
|
||||
|
||||
from enum import Enum
|
||||
|
||||
from models.database.admin_menu_config import FrontendType
|
||||
|
||||
|
||||
class AdminMenuItem(str, Enum):
|
||||
"""Admin frontend menu item identifiers."""
|
||||
|
||||
# Dashboard (always visible section)
|
||||
DASHBOARD = "dashboard"
|
||||
|
||||
# Super Admin section
|
||||
ADMIN_USERS = "admin-users"
|
||||
|
||||
# Platform Administration section
|
||||
COMPANIES = "companies"
|
||||
VENDORS = "vendors"
|
||||
MESSAGES = "messages"
|
||||
|
||||
# Vendor Operations section
|
||||
VENDOR_PRODUCTS = "vendor-products"
|
||||
CUSTOMERS = "customers"
|
||||
INVENTORY = "inventory"
|
||||
ORDERS = "orders"
|
||||
|
||||
# Marketplace section
|
||||
MARKETPLACE_LETZSHOP = "marketplace-letzshop"
|
||||
|
||||
# Billing & Subscriptions section
|
||||
SUBSCRIPTION_TIERS = "subscription-tiers"
|
||||
SUBSCRIPTIONS = "subscriptions"
|
||||
BILLING_HISTORY = "billing-history"
|
||||
|
||||
# Content Management section
|
||||
PLATFORMS = "platforms"
|
||||
CONTENT_PAGES = "content-pages"
|
||||
VENDOR_THEMES = "vendor-themes"
|
||||
|
||||
# Developer Tools section
|
||||
COMPONENTS = "components"
|
||||
ICONS = "icons"
|
||||
|
||||
# Platform Health section
|
||||
PLATFORM_HEALTH = "platform-health"
|
||||
TESTING = "testing"
|
||||
CODE_QUALITY = "code-quality"
|
||||
|
||||
# Platform Monitoring section
|
||||
IMPORTS = "imports"
|
||||
BACKGROUND_TASKS = "background-tasks"
|
||||
LOGS = "logs"
|
||||
NOTIFICATIONS = "notifications"
|
||||
|
||||
# Platform Settings section
|
||||
SETTINGS = "settings"
|
||||
EMAIL_TEMPLATES = "email-templates"
|
||||
MY_MENU = "my-menu" # Super admin only - personal menu configuration
|
||||
|
||||
|
||||
class VendorMenuItem(str, Enum):
|
||||
"""Vendor frontend menu item identifiers."""
|
||||
|
||||
# Main section (always visible)
|
||||
DASHBOARD = "dashboard"
|
||||
ANALYTICS = "analytics"
|
||||
|
||||
# Products & Inventory section
|
||||
PRODUCTS = "products"
|
||||
INVENTORY = "inventory"
|
||||
MARKETPLACE = "marketplace"
|
||||
|
||||
# Sales & Orders section
|
||||
ORDERS = "orders"
|
||||
LETZSHOP = "letzshop"
|
||||
INVOICES = "invoices"
|
||||
|
||||
# Customers section
|
||||
CUSTOMERS = "customers"
|
||||
MESSAGES = "messages"
|
||||
NOTIFICATIONS = "notifications"
|
||||
|
||||
# Shop & Content section
|
||||
CONTENT_PAGES = "content-pages"
|
||||
MEDIA = "media"
|
||||
|
||||
# Account & Settings section
|
||||
TEAM = "team"
|
||||
PROFILE = "profile"
|
||||
BILLING = "billing"
|
||||
EMAIL_TEMPLATES = "email-templates"
|
||||
SETTINGS = "settings"
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Admin Menu Registry
|
||||
# =============================================================================
|
||||
|
||||
ADMIN_MENU_REGISTRY = {
|
||||
"frontend_type": FrontendType.ADMIN,
|
||||
"sections": [
|
||||
{
|
||||
"id": "main",
|
||||
"label": None, # No header, always at top
|
||||
"items": [
|
||||
{
|
||||
"id": AdminMenuItem.DASHBOARD.value,
|
||||
"label": "Dashboard",
|
||||
"icon": "home",
|
||||
"url": "/admin/dashboard",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": "superAdmin",
|
||||
"label": "Super Admin",
|
||||
"super_admin_only": True,
|
||||
"items": [
|
||||
{
|
||||
"id": AdminMenuItem.ADMIN_USERS.value,
|
||||
"label": "Admin Users",
|
||||
"icon": "shield",
|
||||
"url": "/admin/admin-users",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": "platformAdmin",
|
||||
"label": "Platform Administration",
|
||||
"items": [
|
||||
{
|
||||
"id": AdminMenuItem.COMPANIES.value,
|
||||
"label": "Companies",
|
||||
"icon": "office-building",
|
||||
"url": "/admin/companies",
|
||||
},
|
||||
{
|
||||
"id": AdminMenuItem.VENDORS.value,
|
||||
"label": "Vendors",
|
||||
"icon": "shopping-bag",
|
||||
"url": "/admin/vendors",
|
||||
},
|
||||
{
|
||||
"id": AdminMenuItem.MESSAGES.value,
|
||||
"label": "Messages",
|
||||
"icon": "chat-bubble-left-right",
|
||||
"url": "/admin/messages",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": "vendorOps",
|
||||
"label": "Vendor Operations",
|
||||
"items": [
|
||||
{
|
||||
"id": AdminMenuItem.VENDOR_PRODUCTS.value,
|
||||
"label": "Products",
|
||||
"icon": "cube",
|
||||
"url": "/admin/vendor-products",
|
||||
},
|
||||
{
|
||||
"id": AdminMenuItem.CUSTOMERS.value,
|
||||
"label": "Customers",
|
||||
"icon": "user-group",
|
||||
"url": "/admin/customers",
|
||||
},
|
||||
{
|
||||
"id": AdminMenuItem.INVENTORY.value,
|
||||
"label": "Inventory",
|
||||
"icon": "archive",
|
||||
"url": "/admin/inventory",
|
||||
},
|
||||
{
|
||||
"id": AdminMenuItem.ORDERS.value,
|
||||
"label": "Orders",
|
||||
"icon": "clipboard-list",
|
||||
"url": "/admin/orders",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": "marketplace",
|
||||
"label": "Marketplace",
|
||||
"items": [
|
||||
{
|
||||
"id": AdminMenuItem.MARKETPLACE_LETZSHOP.value,
|
||||
"label": "Letzshop",
|
||||
"icon": "shopping-cart",
|
||||
"url": "/admin/marketplace/letzshop",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": "billing",
|
||||
"label": "Billing & Subscriptions",
|
||||
"items": [
|
||||
{
|
||||
"id": AdminMenuItem.SUBSCRIPTION_TIERS.value,
|
||||
"label": "Subscription Tiers",
|
||||
"icon": "tag",
|
||||
"url": "/admin/subscription-tiers",
|
||||
},
|
||||
{
|
||||
"id": AdminMenuItem.SUBSCRIPTIONS.value,
|
||||
"label": "Vendor Subscriptions",
|
||||
"icon": "credit-card",
|
||||
"url": "/admin/subscriptions",
|
||||
},
|
||||
{
|
||||
"id": AdminMenuItem.BILLING_HISTORY.value,
|
||||
"label": "Billing History",
|
||||
"icon": "document-text",
|
||||
"url": "/admin/billing-history",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": "contentMgmt",
|
||||
"label": "Content Management",
|
||||
"items": [
|
||||
{
|
||||
"id": AdminMenuItem.PLATFORMS.value,
|
||||
"label": "Platforms",
|
||||
"icon": "globe-alt",
|
||||
"url": "/admin/platforms",
|
||||
},
|
||||
{
|
||||
"id": AdminMenuItem.CONTENT_PAGES.value,
|
||||
"label": "Content Pages",
|
||||
"icon": "document-text",
|
||||
"url": "/admin/content-pages",
|
||||
},
|
||||
{
|
||||
"id": AdminMenuItem.VENDOR_THEMES.value,
|
||||
"label": "Vendor Themes",
|
||||
"icon": "color-swatch",
|
||||
"url": "/admin/vendor-themes",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": "devTools",
|
||||
"label": "Developer Tools",
|
||||
"items": [
|
||||
{
|
||||
"id": AdminMenuItem.COMPONENTS.value,
|
||||
"label": "Components",
|
||||
"icon": "view-grid",
|
||||
"url": "/admin/components",
|
||||
},
|
||||
{
|
||||
"id": AdminMenuItem.ICONS.value,
|
||||
"label": "Icons",
|
||||
"icon": "photograph",
|
||||
"url": "/admin/icons",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": "platformHealth",
|
||||
"label": "Platform Health",
|
||||
"items": [
|
||||
{
|
||||
"id": AdminMenuItem.PLATFORM_HEALTH.value,
|
||||
"label": "Capacity Monitor",
|
||||
"icon": "chart-bar",
|
||||
"url": "/admin/platform-health",
|
||||
},
|
||||
{
|
||||
"id": AdminMenuItem.TESTING.value,
|
||||
"label": "Testing Hub",
|
||||
"icon": "beaker",
|
||||
"url": "/admin/testing",
|
||||
},
|
||||
{
|
||||
"id": AdminMenuItem.CODE_QUALITY.value,
|
||||
"label": "Code Quality",
|
||||
"icon": "shield-check",
|
||||
"url": "/admin/code-quality",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": "monitoring",
|
||||
"label": "Platform Monitoring",
|
||||
"items": [
|
||||
{
|
||||
"id": AdminMenuItem.IMPORTS.value,
|
||||
"label": "Import Jobs",
|
||||
"icon": "cube",
|
||||
"url": "/admin/imports",
|
||||
},
|
||||
{
|
||||
"id": AdminMenuItem.BACKGROUND_TASKS.value,
|
||||
"label": "Background Tasks",
|
||||
"icon": "collection",
|
||||
"url": "/admin/background-tasks",
|
||||
},
|
||||
{
|
||||
"id": AdminMenuItem.LOGS.value,
|
||||
"label": "Application Logs",
|
||||
"icon": "document-text",
|
||||
"url": "/admin/logs",
|
||||
},
|
||||
{
|
||||
"id": AdminMenuItem.NOTIFICATIONS.value,
|
||||
"label": "Notifications",
|
||||
"icon": "bell",
|
||||
"url": "/admin/notifications",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": "settingsSection",
|
||||
"label": "Platform Settings",
|
||||
"items": [
|
||||
{
|
||||
"id": AdminMenuItem.SETTINGS.value,
|
||||
"label": "General",
|
||||
"icon": "cog",
|
||||
"url": "/admin/settings",
|
||||
},
|
||||
{
|
||||
"id": AdminMenuItem.EMAIL_TEMPLATES.value,
|
||||
"label": "Email Templates",
|
||||
"icon": "mail",
|
||||
"url": "/admin/email-templates",
|
||||
},
|
||||
{
|
||||
"id": AdminMenuItem.MY_MENU.value,
|
||||
"label": "My Menu",
|
||||
"icon": "view-grid",
|
||||
"url": "/admin/my-menu",
|
||||
"super_admin_only": True, # Only super admins can customize their menu
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Vendor Menu Registry
|
||||
# =============================================================================
|
||||
|
||||
VENDOR_MENU_REGISTRY = {
|
||||
"frontend_type": FrontendType.VENDOR,
|
||||
"sections": [
|
||||
{
|
||||
"id": "main",
|
||||
"label": None, # No header, always at top
|
||||
"items": [
|
||||
{
|
||||
"id": VendorMenuItem.DASHBOARD.value,
|
||||
"label": "Dashboard",
|
||||
"icon": "home",
|
||||
"url": "/dashboard", # Relative to /vendor/{code}/
|
||||
},
|
||||
{
|
||||
"id": VendorMenuItem.ANALYTICS.value,
|
||||
"label": "Analytics",
|
||||
"icon": "chart-bar",
|
||||
"url": "/analytics",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": "products",
|
||||
"label": "Products & Inventory",
|
||||
"items": [
|
||||
{
|
||||
"id": VendorMenuItem.PRODUCTS.value,
|
||||
"label": "All Products",
|
||||
"icon": "shopping-bag",
|
||||
"url": "/products",
|
||||
},
|
||||
{
|
||||
"id": VendorMenuItem.INVENTORY.value,
|
||||
"label": "Inventory",
|
||||
"icon": "clipboard-list",
|
||||
"url": "/inventory",
|
||||
},
|
||||
{
|
||||
"id": VendorMenuItem.MARKETPLACE.value,
|
||||
"label": "Marketplace Import",
|
||||
"icon": "download",
|
||||
"url": "/marketplace",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": "sales",
|
||||
"label": "Sales & Orders",
|
||||
"items": [
|
||||
{
|
||||
"id": VendorMenuItem.ORDERS.value,
|
||||
"label": "Orders",
|
||||
"icon": "document-text",
|
||||
"url": "/orders",
|
||||
},
|
||||
{
|
||||
"id": VendorMenuItem.LETZSHOP.value,
|
||||
"label": "Letzshop Orders",
|
||||
"icon": "external-link",
|
||||
"url": "/letzshop",
|
||||
},
|
||||
{
|
||||
"id": VendorMenuItem.INVOICES.value,
|
||||
"label": "Invoices",
|
||||
"icon": "currency-euro",
|
||||
"url": "/invoices",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": "customers",
|
||||
"label": "Customers",
|
||||
"items": [
|
||||
{
|
||||
"id": VendorMenuItem.CUSTOMERS.value,
|
||||
"label": "All Customers",
|
||||
"icon": "user-group",
|
||||
"url": "/customers",
|
||||
},
|
||||
{
|
||||
"id": VendorMenuItem.MESSAGES.value,
|
||||
"label": "Messages",
|
||||
"icon": "chat-bubble-left-right",
|
||||
"url": "/messages",
|
||||
},
|
||||
{
|
||||
"id": VendorMenuItem.NOTIFICATIONS.value,
|
||||
"label": "Notifications",
|
||||
"icon": "bell",
|
||||
"url": "/notifications",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": "shop",
|
||||
"label": "Shop & Content",
|
||||
"items": [
|
||||
{
|
||||
"id": VendorMenuItem.CONTENT_PAGES.value,
|
||||
"label": "Content Pages",
|
||||
"icon": "document-text",
|
||||
"url": "/content-pages",
|
||||
},
|
||||
{
|
||||
"id": VendorMenuItem.MEDIA.value,
|
||||
"label": "Media Library",
|
||||
"icon": "photograph",
|
||||
"url": "/media",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": "account",
|
||||
"label": "Account & Settings",
|
||||
"items": [
|
||||
{
|
||||
"id": VendorMenuItem.TEAM.value,
|
||||
"label": "Team",
|
||||
"icon": "user-group",
|
||||
"url": "/team",
|
||||
},
|
||||
{
|
||||
"id": VendorMenuItem.PROFILE.value,
|
||||
"label": "Profile",
|
||||
"icon": "user",
|
||||
"url": "/profile",
|
||||
},
|
||||
{
|
||||
"id": VendorMenuItem.BILLING.value,
|
||||
"label": "Billing",
|
||||
"icon": "credit-card",
|
||||
"url": "/billing",
|
||||
},
|
||||
{
|
||||
"id": VendorMenuItem.EMAIL_TEMPLATES.value,
|
||||
"label": "Email Templates",
|
||||
"icon": "mail",
|
||||
"url": "/email-templates",
|
||||
},
|
||||
{
|
||||
"id": VendorMenuItem.SETTINGS.value,
|
||||
"label": "Settings",
|
||||
"icon": "adjustments",
|
||||
"url": "/settings",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Helper Functions
|
||||
# =============================================================================
|
||||
|
||||
|
||||
def get_all_menu_item_ids(frontend_type: FrontendType) -> set[str]:
|
||||
"""Get all menu item IDs for a frontend type."""
|
||||
registry = (
|
||||
ADMIN_MENU_REGISTRY if frontend_type == FrontendType.ADMIN else VENDOR_MENU_REGISTRY
|
||||
)
|
||||
items = set()
|
||||
for section in registry["sections"]:
|
||||
for item in section["items"]:
|
||||
items.add(item["id"])
|
||||
return items
|
||||
|
||||
|
||||
def get_menu_item(frontend_type: FrontendType, menu_item_id: str) -> dict | None:
|
||||
"""Get a menu item definition by ID."""
|
||||
registry = (
|
||||
ADMIN_MENU_REGISTRY if frontend_type == FrontendType.ADMIN else VENDOR_MENU_REGISTRY
|
||||
)
|
||||
for section in registry["sections"]:
|
||||
for item in section["items"]:
|
||||
if item["id"] == menu_item_id:
|
||||
return {**item, "section_id": section["id"], "section_label": section.get("label")}
|
||||
return None
|
||||
|
||||
|
||||
def is_super_admin_only_item(menu_item_id: str) -> bool:
|
||||
"""Check if a menu item is in a super_admin_only section."""
|
||||
for section in ADMIN_MENU_REGISTRY["sections"]:
|
||||
if section.get("super_admin_only"):
|
||||
for item in section["items"]:
|
||||
if item["id"] == menu_item_id:
|
||||
return True
|
||||
return False
|
||||
@@ -20,7 +20,7 @@ from sqlalchemy.orm import Session
|
||||
from app.exceptions.platform import (
|
||||
PlatformNotFoundException,
|
||||
)
|
||||
from models.database.content_page import ContentPage
|
||||
from app.modules.cms.models import ContentPage
|
||||
from models.database.platform import Platform
|
||||
from models.database.vendor_platform import VendorPlatform
|
||||
|
||||
@@ -81,6 +81,28 @@ class PlatformService:
|
||||
"""
|
||||
return db.query(Platform).filter(Platform.code == code).first()
|
||||
|
||||
@staticmethod
|
||||
def get_platform_by_id(db: Session, platform_id: int) -> Platform:
|
||||
"""
|
||||
Get platform by ID.
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
platform_id: Platform ID
|
||||
|
||||
Returns:
|
||||
Platform object
|
||||
|
||||
Raises:
|
||||
PlatformNotFoundException: If platform not found
|
||||
"""
|
||||
platform = db.query(Platform).filter(Platform.id == platform_id).first()
|
||||
|
||||
if not platform:
|
||||
raise PlatformNotFoundException(str(platform_id))
|
||||
|
||||
return platform
|
||||
|
||||
@staticmethod
|
||||
def list_platforms(
|
||||
db: Session, include_inactive: bool = False
|
||||
|
||||
174
app/templates/admin/my-menu-config.html
Normal file
174
app/templates/admin/my-menu-config.html
Normal file
@@ -0,0 +1,174 @@
|
||||
{# app/templates/admin/my-menu-config.html #}
|
||||
{% extends "admin/base.html" %}
|
||||
{% from 'shared/macros/alerts.html' import alert_dynamic, error_state %}
|
||||
{% from 'shared/macros/headers.html' import page_header %}
|
||||
|
||||
{% block title %}My Menu{% endblock %}
|
||||
|
||||
{% block alpine_data %}adminMyMenuConfig(){% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{{ page_header('My Menu Configuration', subtitle='Customize your personal admin sidebar', back_url='/admin/settings') }}
|
||||
|
||||
{{ alert_dynamic(type='success', title='Success', message_var='successMessage', show_condition='successMessage') }}
|
||||
{{ error_state('Error', show_condition='error') }}
|
||||
|
||||
<!-- Info Box -->
|
||||
<div class="mb-6 p-4 bg-blue-50 dark:bg-blue-900/20 rounded-lg border border-blue-200 dark:border-blue-800">
|
||||
<div class="flex items-start">
|
||||
<span x-html="$icon('information-circle', 'w-5 h-5 text-blue-600 dark:text-blue-400 mt-0.5 mr-3')"></span>
|
||||
<div>
|
||||
<p class="text-sm text-blue-800 dark:text-blue-200">
|
||||
This configures <strong>your personal</strong> admin sidebar menu. These settings only affect your view.
|
||||
</p>
|
||||
<p class="text-sm text-blue-700 dark:text-blue-300 mt-1">
|
||||
To configure menus for platform admins or vendors, go to <a href="/admin/platforms" class="underline hover:no-underline">Platforms</a> and select a platform's Menu Configuration.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stats Cards -->
|
||||
<div class="grid gap-4 mb-6 md:grid-cols-3">
|
||||
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
||||
<div class="p-3 mr-4 text-blue-500 bg-blue-100 rounded-full dark:text-blue-100 dark:bg-blue-500">
|
||||
<span x-html="$icon('view-grid', 'w-5 h-5')"></span>
|
||||
</div>
|
||||
<div>
|
||||
<p class="mb-1 text-sm font-medium text-gray-600 dark:text-gray-400">Total Items</p>
|
||||
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="menuConfig?.total_items || 0"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
||||
<div class="p-3 mr-4 text-green-500 bg-green-100 rounded-full dark:text-green-100 dark:bg-green-500">
|
||||
<span x-html="$icon('eye', 'w-5 h-5')"></span>
|
||||
</div>
|
||||
<div>
|
||||
<p class="mb-1 text-sm font-medium text-gray-600 dark:text-gray-400">Visible</p>
|
||||
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="menuConfig?.visible_items || 0"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
||||
<div class="p-3 mr-4 text-gray-500 bg-gray-100 rounded-full dark:text-gray-100 dark:bg-gray-600">
|
||||
<span x-html="$icon('eye-off', 'w-5 h-5')"></span>
|
||||
</div>
|
||||
<div>
|
||||
<p class="mb-1 text-sm font-medium text-gray-600 dark:text-gray-400">Hidden</p>
|
||||
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="menuConfig?.hidden_items || 0"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
Toggle visibility for menu items. Mandatory items cannot be hidden.
|
||||
</p>
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
@click="showAll()"
|
||||
:disabled="saving"
|
||||
class="inline-flex items-center px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-600 dark:hover:bg-gray-600"
|
||||
>
|
||||
<span x-html="$icon('eye', 'w-4 h-4 mr-2')"></span>
|
||||
Show All
|
||||
</button>
|
||||
<button
|
||||
@click="resetToDefaults()"
|
||||
:disabled="saving"
|
||||
class="inline-flex items-center px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-600 dark:hover:bg-gray-600"
|
||||
>
|
||||
<span x-html="$icon('eye-off', 'w-4 h-4 mr-2')"></span>
|
||||
Hide All
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Loading State -->
|
||||
<div x-show="loading" class="flex items-center justify-center py-12 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
||||
<span x-html="$icon('refresh', 'w-8 h-8 animate-spin text-purple-600')"></span>
|
||||
<span class="ml-3 text-gray-500 dark:text-gray-400">Loading menu configuration...</span>
|
||||
</div>
|
||||
|
||||
<!-- Menu Items by Section -->
|
||||
<div x-show="!loading" class="space-y-6">
|
||||
<template x-for="section in groupedItems" :key="section.id">
|
||||
<div class="bg-white rounded-lg shadow-xs dark:bg-gray-800 overflow-hidden">
|
||||
<!-- Section Header -->
|
||||
<div class="px-4 py-3 bg-gray-50 dark:bg-gray-700/50 border-b border-gray-200 dark:border-gray-700">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<h3 class="text-sm font-semibold text-gray-700 dark:text-gray-200" x-text="section.label || 'Main'"></h3>
|
||||
<span
|
||||
x-show="section.isSuperAdminOnly"
|
||||
class="ml-2 px-2 py-0.5 text-xs font-medium rounded bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200"
|
||||
>
|
||||
Super Admin Only
|
||||
</span>
|
||||
</div>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400" x-text="`${section.visibleCount}/${section.items.length} visible`"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Section Items -->
|
||||
<div class="divide-y divide-gray-100 dark:divide-gray-700">
|
||||
<template x-for="item in section.items" :key="item.id">
|
||||
<div class="flex items-center justify-between px-4 py-3 hover:bg-gray-50 dark:hover:bg-gray-700/30">
|
||||
<div class="flex items-center">
|
||||
<span x-html="$icon(item.icon, 'w-5 h-5 text-gray-400 mr-3')"></span>
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-700 dark:text-gray-200" x-text="item.label"></p>
|
||||
<p class="text-xs text-gray-400 dark:text-gray-500" x-text="item.url"></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<!-- Mandatory Badge -->
|
||||
<span
|
||||
x-show="item.is_mandatory"
|
||||
class="px-2 py-0.5 text-xs font-medium rounded bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200"
|
||||
>
|
||||
Mandatory
|
||||
</span>
|
||||
|
||||
<!-- Toggle Switch -->
|
||||
<button
|
||||
@click="toggleVisibility(item)"
|
||||
:disabled="item.is_mandatory || saving"
|
||||
:class="{
|
||||
'bg-purple-600': item.is_visible,
|
||||
'bg-gray-200 dark:bg-gray-600': !item.is_visible,
|
||||
'opacity-50 cursor-not-allowed': item.is_mandatory
|
||||
}"
|
||||
class="relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2"
|
||||
role="switch"
|
||||
:aria-checked="item.is_visible"
|
||||
>
|
||||
<span
|
||||
:class="{
|
||||
'translate-x-5': item.is_visible,
|
||||
'translate-x-0': !item.is_visible
|
||||
}"
|
||||
class="pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"
|
||||
></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Empty State -->
|
||||
<div x-show="groupedItems.length === 0" class="bg-white rounded-lg shadow-xs dark:bg-gray-800 p-8 text-center">
|
||||
<span x-html="$icon('view-grid', 'w-12 h-12 mx-auto text-gray-400')"></span>
|
||||
<p class="mt-4 text-gray-500 dark:text-gray-400">No menu items available.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_scripts %}
|
||||
<script src="{{ url_for('static', path='admin/js/my-menu-config.js') }}"></script>
|
||||
{% endblock %}
|
||||
@@ -38,9 +38,9 @@
|
||||
</ul>
|
||||
{% endmacro %}
|
||||
|
||||
{# Macro for menu item #}
|
||||
{# Macro for menu item with visibility check #}
|
||||
{% macro menu_item(page_id, url, icon, label) %}
|
||||
<li class="relative px-6 py-3">
|
||||
<li x-show="isMenuItemVisible('{{ page_id }}')" class="relative px-6 py-3">
|
||||
<span x-show="currentPage === '{{ page_id }}'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
||||
:class="currentPage === '{{ page_id }}' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
@@ -56,7 +56,7 @@
|
||||
============================================================================ #}
|
||||
|
||||
{% macro sidebar_content() %}
|
||||
<div class="py-4 text-gray-500 dark:text-gray-400">
|
||||
<div class="py-4 text-gray-500 dark:text-gray-400" x-init="loadMenuConfig()">
|
||||
<a class="ml-6 text-lg font-bold text-gray-800 dark:text-gray-200" href="/admin/dashboard">
|
||||
Admin Portal
|
||||
</a>
|
||||
@@ -77,80 +77,110 @@
|
||||
</template>
|
||||
|
||||
<!-- Platform Administration Section -->
|
||||
{{ section_header('Platform Administration', 'platformAdmin') }}
|
||||
{% call section_content('platformAdmin') %}
|
||||
{{ menu_item('companies', '/admin/companies', 'office-building', 'Companies') }}
|
||||
{{ menu_item('vendors', '/admin/vendors', 'shopping-bag', 'Vendors') }}
|
||||
{{ menu_item('messages', '/admin/messages', 'chat-bubble-left-right', 'Messages') }}
|
||||
{% endcall %}
|
||||
<div x-show="isSectionVisible('platformAdmin')">
|
||||
{{ section_header('Platform Administration', 'platformAdmin') }}
|
||||
{% call section_content('platformAdmin') %}
|
||||
{{ menu_item('companies', '/admin/companies', 'office-building', 'Companies') }}
|
||||
{{ menu_item('vendors', '/admin/vendors', 'shopping-bag', 'Vendors') }}
|
||||
{{ menu_item('messages', '/admin/messages', 'chat-bubble-left-right', 'Messages') }}
|
||||
{% endcall %}
|
||||
</div>
|
||||
|
||||
<!-- Vendor Operations Section -->
|
||||
{{ section_header('Vendor Operations', 'vendorOps') }}
|
||||
{% call section_content('vendorOps') %}
|
||||
{{ menu_item('vendor-products', '/admin/vendor-products', 'cube', 'Products') }}
|
||||
{{ menu_item('customers', '/admin/customers', 'user-group', 'Customers') }}
|
||||
{{ menu_item('inventory', '/admin/inventory', 'archive', 'Inventory') }}
|
||||
{{ menu_item('orders', '/admin/orders', 'clipboard-list', 'Orders') }}
|
||||
{# Future items - uncomment when implemented:
|
||||
{{ menu_item('shipping', '/admin/shipping', 'truck', 'Shipping') }}
|
||||
#}
|
||||
{% endcall %}
|
||||
<div x-show="isSectionVisible('vendorOps')">
|
||||
{{ section_header('Vendor Operations', 'vendorOps') }}
|
||||
{% call section_content('vendorOps') %}
|
||||
{{ menu_item('vendor-products', '/admin/vendor-products', 'cube', 'Products') }}
|
||||
{{ menu_item('customers', '/admin/customers', 'user-group', 'Customers') }}
|
||||
{{ menu_item('inventory', '/admin/inventory', 'archive', 'Inventory') }}
|
||||
{{ menu_item('orders', '/admin/orders', 'clipboard-list', 'Orders') }}
|
||||
{# Future items - uncomment when implemented:
|
||||
{{ menu_item('shipping', '/admin/shipping', 'truck', 'Shipping') }}
|
||||
#}
|
||||
{% endcall %}
|
||||
</div>
|
||||
|
||||
<!-- Marketplace Section -->
|
||||
{{ section_header('Marketplace', 'marketplace') }}
|
||||
{% call section_content('marketplace') %}
|
||||
{{ menu_item('marketplace-letzshop', '/admin/marketplace/letzshop', 'shopping-cart', 'Letzshop') }}
|
||||
{% endcall %}
|
||||
<div x-show="isSectionVisible('marketplace')">
|
||||
{{ section_header('Marketplace', 'marketplace') }}
|
||||
{% call section_content('marketplace') %}
|
||||
{{ menu_item('marketplace-letzshop', '/admin/marketplace/letzshop', 'shopping-cart', 'Letzshop') }}
|
||||
{% endcall %}
|
||||
</div>
|
||||
|
||||
<!-- Billing & Subscriptions Section -->
|
||||
{{ section_header('Billing & Subscriptions', 'billing') }}
|
||||
{% call section_content('billing') %}
|
||||
{{ menu_item('subscription-tiers', '/admin/subscription-tiers', 'tag', 'Subscription Tiers') }}
|
||||
{{ menu_item('subscriptions', '/admin/subscriptions', 'credit-card', 'Vendor Subscriptions') }}
|
||||
{{ menu_item('billing-history', '/admin/billing-history', 'document-text', 'Billing History') }}
|
||||
{% endcall %}
|
||||
<div x-show="isSectionVisible('billing')">
|
||||
{{ section_header('Billing & Subscriptions', 'billing') }}
|
||||
{% call section_content('billing') %}
|
||||
{{ menu_item('subscription-tiers', '/admin/subscription-tiers', 'tag', 'Subscription Tiers') }}
|
||||
{{ menu_item('subscriptions', '/admin/subscriptions', 'credit-card', 'Vendor Subscriptions') }}
|
||||
{{ menu_item('billing-history', '/admin/billing-history', 'document-text', 'Billing History') }}
|
||||
{% endcall %}
|
||||
</div>
|
||||
|
||||
<!-- Content Management Section -->
|
||||
{{ section_header('Content Management', 'contentMgmt') }}
|
||||
{% call section_content('contentMgmt') %}
|
||||
{{ menu_item('platforms', '/admin/platforms', 'globe-alt', 'Platforms') }}
|
||||
{{ menu_item('content-pages', '/admin/content-pages', 'document-text', 'Content Pages') }}
|
||||
{{ menu_item('vendor-theme', '/admin/vendor-themes', 'color-swatch', 'Vendor Themes') }}
|
||||
{% endcall %}
|
||||
<div x-show="isSectionVisible('contentMgmt')">
|
||||
{{ section_header('Content Management', 'contentMgmt') }}
|
||||
{% call section_content('contentMgmt') %}
|
||||
{{ menu_item('platforms', '/admin/platforms', 'globe-alt', 'Platforms') }}
|
||||
{{ menu_item('content-pages', '/admin/content-pages', 'document-text', 'Content Pages') }}
|
||||
{{ menu_item('vendor-themes', '/admin/vendor-themes', 'color-swatch', 'Vendor Themes') }}
|
||||
{% endcall %}
|
||||
</div>
|
||||
|
||||
<!-- Developer Tools Section -->
|
||||
{{ section_header('Developer Tools', 'devTools') }}
|
||||
{% call section_content('devTools') %}
|
||||
{{ menu_item('components', '/admin/components', 'view-grid', 'Components') }}
|
||||
{{ menu_item('icons', '/admin/icons', 'photograph', 'Icons') }}
|
||||
{% endcall %}
|
||||
<div x-show="isSectionVisible('devTools')">
|
||||
{{ section_header('Developer Tools', 'devTools') }}
|
||||
{% call section_content('devTools') %}
|
||||
{{ menu_item('components', '/admin/components', 'view-grid', 'Components') }}
|
||||
{{ menu_item('icons', '/admin/icons', 'photograph', 'Icons') }}
|
||||
{% endcall %}
|
||||
</div>
|
||||
|
||||
<!-- Platform Health Section -->
|
||||
{{ section_header('Platform Health', 'platformHealth') }}
|
||||
{% call section_content('platformHealth') %}
|
||||
{{ menu_item('platform-health', '/admin/platform-health', 'chart-bar', 'Capacity Monitor') }}
|
||||
{{ menu_item('testing', '/admin/testing', 'beaker', 'Testing Hub') }}
|
||||
{{ menu_item('code-quality', '/admin/code-quality', 'shield-check', 'Code Quality') }}
|
||||
{% endcall %}
|
||||
<div x-show="isSectionVisible('platformHealth')">
|
||||
{{ section_header('Platform Health', 'platformHealth') }}
|
||||
{% call section_content('platformHealth') %}
|
||||
{{ menu_item('platform-health', '/admin/platform-health', 'chart-bar', 'Capacity Monitor') }}
|
||||
{{ menu_item('testing', '/admin/testing', 'beaker', 'Testing Hub') }}
|
||||
{{ menu_item('code-quality', '/admin/code-quality', 'shield-check', 'Code Quality') }}
|
||||
{% endcall %}
|
||||
</div>
|
||||
|
||||
<!-- Platform Monitoring Section -->
|
||||
{{ section_header('Platform Monitoring', 'monitoring') }}
|
||||
{% call section_content('monitoring') %}
|
||||
{{ menu_item('imports', '/admin/imports', 'cube', 'Import Jobs') }}
|
||||
{{ menu_item('background-tasks', '/admin/background-tasks', 'collection', 'Background Tasks') }}
|
||||
{{ menu_item('logs', '/admin/logs', 'document-text', 'Application Logs') }}
|
||||
{{ menu_item('notifications', '/admin/notifications', 'bell', 'Notifications') }}
|
||||
{% endcall %}
|
||||
<div x-show="isSectionVisible('monitoring')">
|
||||
{{ section_header('Platform Monitoring', 'monitoring') }}
|
||||
{% call section_content('monitoring') %}
|
||||
{{ menu_item('imports', '/admin/imports', 'cube', 'Import Jobs') }}
|
||||
{{ menu_item('background-tasks', '/admin/background-tasks', 'collection', 'Background Tasks') }}
|
||||
{{ menu_item('logs', '/admin/logs', 'document-text', 'Application Logs') }}
|
||||
{{ menu_item('notifications', '/admin/notifications', 'bell', 'Notifications') }}
|
||||
{% endcall %}
|
||||
</div>
|
||||
|
||||
<!-- Platform Settings Section -->
|
||||
{{ section_header('Platform Settings', 'settingsSection') }}
|
||||
{% call section_content('settingsSection') %}
|
||||
{{ menu_item('settings', '/admin/settings', 'cog', 'General') }}
|
||||
{{ menu_item('email-templates', '/admin/email-templates', 'mail', 'Email Templates') }}
|
||||
{# TODO: Implement profile and API keys pages #}
|
||||
{# {{ menu_item('profile', '/admin/profile', 'user-circle', 'Profile') }} #}
|
||||
{# {{ menu_item('api-keys', '/admin/api-keys', 'key', 'API Keys') }} #}
|
||||
{% endcall %}
|
||||
<div x-show="isSectionVisible('settingsSection')">
|
||||
{{ section_header('Platform Settings', 'settingsSection') }}
|
||||
{% call section_content('settingsSection') %}
|
||||
{{ menu_item('settings', '/admin/settings', 'cog', 'General') }}
|
||||
{{ menu_item('email-templates', '/admin/email-templates', 'mail', 'Email Templates') }}
|
||||
<!-- My Menu - super admin only (customize personal sidebar) -->
|
||||
<template x-if="isSuperAdmin">
|
||||
<li x-show="isMenuItemVisible('my-menu')" class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'my-menu'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
||||
:class="currentPage === 'my-menu' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/my-menu">
|
||||
<span x-html="$icon('view-grid')"></span>
|
||||
<span class="ml-4">My Menu</span>
|
||||
</a>
|
||||
</li>
|
||||
</template>
|
||||
{# TODO: Implement profile and API keys pages #}
|
||||
{# {{ menu_item('profile', '/admin/profile', 'user-circle', 'Profile') }} #}
|
||||
{# {{ menu_item('api-keys', '/admin/api-keys', 'key', 'API Keys') }} #}
|
||||
{% endcall %}
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
200
app/templates/admin/platform-menu-config.html
Normal file
200
app/templates/admin/platform-menu-config.html
Normal file
@@ -0,0 +1,200 @@
|
||||
{# app/templates/admin/platform-menu-config.html #}
|
||||
{% extends "admin/base.html" %}
|
||||
{% from 'shared/macros/alerts.html' import alert_dynamic, error_state %}
|
||||
{% from 'shared/macros/headers.html' import page_header %}
|
||||
|
||||
{% block title %}Menu Configuration{% endblock %}
|
||||
|
||||
{% block alpine_data %}adminPlatformMenuConfig('{{ platform_code }}'){% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{{ page_header('Menu Configuration', back_url='/admin/platforms/' + platform_code) }}
|
||||
|
||||
{{ alert_dynamic(type='success', title='Success', message_var='successMessage', show_condition='successMessage') }}
|
||||
{{ error_state('Error', show_condition='error') }}
|
||||
|
||||
<!-- Platform Info -->
|
||||
<div class="mb-6 p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="platform?.name || 'Loading...'"></h2>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
Configure which menu items are visible for admins and vendors on this platform.
|
||||
</p>
|
||||
</div>
|
||||
<span class="px-3 py-1 text-sm font-medium rounded-full bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200" x-text="platform?.code?.toUpperCase()"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Frontend Type Tabs -->
|
||||
<div class="mb-6">
|
||||
<div class="flex space-x-1 bg-gray-100 dark:bg-gray-700 rounded-lg p-1 w-fit">
|
||||
<button
|
||||
@click="frontendType = 'admin'; loadPlatformMenuConfig()"
|
||||
:class="{
|
||||
'bg-white dark:bg-gray-800 shadow': frontendType === 'admin',
|
||||
'text-gray-600 dark:text-gray-400': frontendType !== 'admin'
|
||||
}"
|
||||
class="px-4 py-2 text-sm font-medium rounded-md transition-all"
|
||||
>
|
||||
<span x-html="$icon('shield', 'w-4 h-4 inline mr-2')"></span>
|
||||
Admin Frontend
|
||||
</button>
|
||||
<button
|
||||
@click="frontendType = 'vendor'; loadPlatformMenuConfig()"
|
||||
:class="{
|
||||
'bg-white dark:bg-gray-800 shadow': frontendType === 'vendor',
|
||||
'text-gray-600 dark:text-gray-400': frontendType !== 'vendor'
|
||||
}"
|
||||
class="px-4 py-2 text-sm font-medium rounded-md transition-all"
|
||||
>
|
||||
<span x-html="$icon('shopping-bag', 'w-4 h-4 inline mr-2')"></span>
|
||||
Vendor Frontend
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stats Cards -->
|
||||
<div class="grid gap-4 mb-6 md:grid-cols-3">
|
||||
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
||||
<div class="p-3 mr-4 text-blue-500 bg-blue-100 rounded-full dark:text-blue-100 dark:bg-blue-500">
|
||||
<span x-html="$icon('view-grid', 'w-5 h-5')"></span>
|
||||
</div>
|
||||
<div>
|
||||
<p class="mb-1 text-sm font-medium text-gray-600 dark:text-gray-400">Total Items</p>
|
||||
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="menuConfig?.total_items || 0"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
||||
<div class="p-3 mr-4 text-green-500 bg-green-100 rounded-full dark:text-green-100 dark:bg-green-500">
|
||||
<span x-html="$icon('eye', 'w-5 h-5')"></span>
|
||||
</div>
|
||||
<div>
|
||||
<p class="mb-1 text-sm font-medium text-gray-600 dark:text-gray-400">Visible</p>
|
||||
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="menuConfig?.visible_items || 0"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
||||
<div class="p-3 mr-4 text-gray-500 bg-gray-100 rounded-full dark:text-gray-100 dark:bg-gray-600">
|
||||
<span x-html="$icon('eye-off', 'w-5 h-5')"></span>
|
||||
</div>
|
||||
<div>
|
||||
<p class="mb-1 text-sm font-medium text-gray-600 dark:text-gray-400">Hidden</p>
|
||||
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="menuConfig?.hidden_items || 0"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
Toggle visibility for menu items. Mandatory items cannot be hidden.
|
||||
</p>
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
@click="showAll()"
|
||||
:disabled="saving"
|
||||
class="inline-flex items-center px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-600 dark:hover:bg-gray-600"
|
||||
>
|
||||
<span x-html="$icon('eye', 'w-4 h-4 mr-2')"></span>
|
||||
Show All
|
||||
</button>
|
||||
<button
|
||||
@click="resetToDefaults()"
|
||||
:disabled="saving"
|
||||
class="inline-flex items-center px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-600 dark:hover:bg-gray-600"
|
||||
>
|
||||
<span x-html="$icon('eye-off', 'w-4 h-4 mr-2')"></span>
|
||||
Hide All
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Loading State -->
|
||||
<div x-show="loading" class="flex items-center justify-center py-12 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
||||
<span x-html="$icon('refresh', 'w-8 h-8 animate-spin text-purple-600')"></span>
|
||||
<span class="ml-3 text-gray-500 dark:text-gray-400">Loading menu configuration...</span>
|
||||
</div>
|
||||
|
||||
<!-- Menu Items by Section -->
|
||||
<div x-show="!loading" class="space-y-6">
|
||||
<template x-for="section in groupedItems" :key="section.id">
|
||||
<div class="bg-white rounded-lg shadow-xs dark:bg-gray-800 overflow-hidden">
|
||||
<!-- Section Header -->
|
||||
<div class="px-4 py-3 bg-gray-50 dark:bg-gray-700/50 border-b border-gray-200 dark:border-gray-700">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<h3 class="text-sm font-semibold text-gray-700 dark:text-gray-200" x-text="section.label || 'Main'"></h3>
|
||||
<span
|
||||
x-show="section.isSuperAdminOnly"
|
||||
class="ml-2 px-2 py-0.5 text-xs font-medium rounded bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200"
|
||||
>
|
||||
Super Admin Only
|
||||
</span>
|
||||
</div>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400" x-text="`${section.visibleCount}/${section.items.length} visible`"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Section Items -->
|
||||
<div class="divide-y divide-gray-100 dark:divide-gray-700">
|
||||
<template x-for="item in section.items" :key="item.id">
|
||||
<div class="flex items-center justify-between px-4 py-3 hover:bg-gray-50 dark:hover:bg-gray-700/30">
|
||||
<div class="flex items-center">
|
||||
<span x-html="$icon(item.icon, 'w-5 h-5 text-gray-400 mr-3')"></span>
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-700 dark:text-gray-200" x-text="item.label"></p>
|
||||
<p class="text-xs text-gray-400 dark:text-gray-500" x-text="item.url"></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<!-- Mandatory Badge -->
|
||||
<span
|
||||
x-show="item.is_mandatory"
|
||||
class="px-2 py-0.5 text-xs font-medium rounded bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200"
|
||||
>
|
||||
Mandatory
|
||||
</span>
|
||||
|
||||
<!-- Toggle Switch -->
|
||||
<button
|
||||
@click="toggleVisibility(item)"
|
||||
:disabled="item.is_mandatory || saving"
|
||||
:class="{
|
||||
'bg-purple-600': item.is_visible,
|
||||
'bg-gray-200 dark:bg-gray-600': !item.is_visible,
|
||||
'opacity-50 cursor-not-allowed': item.is_mandatory
|
||||
}"
|
||||
class="relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2"
|
||||
role="switch"
|
||||
:aria-checked="item.is_visible"
|
||||
>
|
||||
<span
|
||||
:class="{
|
||||
'translate-x-5': item.is_visible,
|
||||
'translate-x-0': !item.is_visible
|
||||
}"
|
||||
class="pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"
|
||||
></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Empty State -->
|
||||
<div x-show="groupedItems.length === 0" class="bg-white rounded-lg shadow-xs dark:bg-gray-800 p-8 text-center">
|
||||
<span x-html="$icon('view-grid', 'w-12 h-12 mx-auto text-gray-400')"></span>
|
||||
<p class="mt-4 text-gray-500 dark:text-gray-400">No menu items configured for this frontend type.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_scripts %}
|
||||
<script src="{{ url_for('static', path='admin/js/platform-menu-config.js') }}"></script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user