@open-mercato/ui 0.4.5-develop-3093b8bee8 → 0.4.5-develop-8a56591995
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/backend/AppShell.js +2 -0
- package/dist/backend/AppShell.js.map +2 -2
- package/dist/backend/progress/ProgressTopBar.js +123 -0
- package/dist/backend/progress/ProgressTopBar.js.map +7 -0
- package/dist/backend/progress/useProgressPoll.js +66 -0
- package/dist/backend/progress/useProgressPoll.js.map +7 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +2 -2
- package/dist/primitives/progress.js +28 -0
- package/dist/primitives/progress.js.map +7 -0
- package/package.json +2 -2
- package/src/backend/AppShell.tsx +2 -0
- package/src/backend/progress/ProgressTopBar.tsx +172 -0
- package/src/backend/progress/useProgressPoll.ts +95 -0
- package/src/index.ts +1 -0
- package/src/primitives/progress.tsx +28 -0
package/dist/backend/AppShell.js
CHANGED
|
@@ -10,6 +10,7 @@ import { FlashMessages } from "./FlashMessages.js";
|
|
|
10
10
|
import { usePathname, useSearchParams } from "next/navigation";
|
|
11
11
|
import { apiCall } from "./utils/apiCall.js";
|
|
12
12
|
import { LastOperationBanner } from "./operations/LastOperationBanner.js";
|
|
13
|
+
import { ProgressTopBar } from "./progress/ProgressTopBar.js";
|
|
13
14
|
import { UpgradeActionBanner } from "./upgrades/UpgradeActionBanner.js";
|
|
14
15
|
import { PartialIndexBanner } from "./indexes/PartialIndexBanner.js";
|
|
15
16
|
import { useLocale, useT } from "@open-mercato/shared/lib/i18n/context";
|
|
@@ -978,6 +979,7 @@ function AppShell({ productName, email, groups, rightHeaderSlot, children, sideb
|
|
|
978
979
|
] }),
|
|
979
980
|
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-1 md:gap-2 text-sm shrink-0", children: rightHeaderSlot ? rightHeaderSlot : /* @__PURE__ */ jsx("span", { className: "opacity-80", children: email || t("appShell.userFallback") }) })
|
|
980
981
|
] }),
|
|
982
|
+
/* @__PURE__ */ jsx(ProgressTopBar, { t, className: "sticky top-0 z-10" }),
|
|
981
983
|
/* @__PURE__ */ jsxs("main", { className: "flex-1 p-4 lg:p-6", children: [
|
|
982
984
|
/* @__PURE__ */ jsx(InjectionSpot, { spotId: BACKEND_LAYOUT_TOP_INJECTION_SPOT_ID, context: injectionContext }),
|
|
983
985
|
/* @__PURE__ */ jsx(FlashMessages, {}),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/backend/AppShell.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport Link from 'next/link'\nimport Image from 'next/image'\nimport { ChevronUp, ChevronDown } from 'lucide-react'\nimport { Button } from '../primitives/button'\nimport { IconButton } from '../primitives/icon-button'\nimport { Separator } from '../primitives/separator'\nimport { FlashMessages } from './FlashMessages'\nimport { usePathname, useSearchParams } from 'next/navigation'\nimport { apiCall } from './utils/apiCall'\nimport { LastOperationBanner } from './operations/LastOperationBanner'\nimport { UpgradeActionBanner } from './upgrades/UpgradeActionBanner'\nimport { PartialIndexBanner } from './indexes/PartialIndexBanner'\nimport { useLocale, useT } from '@open-mercato/shared/lib/i18n/context'\nimport { slugifySidebarId } from '@open-mercato/shared/modules/navigation/sidebarPreferences'\nimport type { SectionNavGroup } from './section-page/types'\nimport { InjectionSpot } from './injection/InjectionSpot'\nimport { LEGACY_GLOBAL_MUTATION_INJECTION_SPOT_ID } from './injection/mutationEvents'\nimport {\n BACKEND_LAYOUT_FOOTER_INJECTION_SPOT_ID,\n BACKEND_LAYOUT_TOP_INJECTION_SPOT_ID,\n BACKEND_RECORD_CURRENT_INJECTION_SPOT_ID,\n BACKEND_SIDEBAR_FOOTER_INJECTION_SPOT_ID,\n BACKEND_SIDEBAR_TOP_INJECTION_SPOT_ID,\n} from './injection/spotIds'\n\nexport type AppShellProps = {\n productName?: string\n email?: string\n groups: {\n id?: string\n name: string\n defaultName?: string\n items: {\n href: string\n title: string\n defaultTitle?: string\n icon?: React.ReactNode\n enabled?: boolean\n hidden?: boolean\n pageContext?: 'main' | 'admin' | 'settings' | 'profile'\n children?: {\n href: string\n title: string\n defaultTitle?: string\n icon?: React.ReactNode\n enabled?: boolean\n hidden?: boolean\n pageContext?: 'main' | 'admin' | 'settings' | 'profile'\n }[]\n }[]\n }[]\n children: React.ReactNode\n rightHeaderSlot?: React.ReactNode\n sidebarCollapsedDefault?: boolean\n currentTitle?: string\n breadcrumb?: Array<{ label: string; href?: string }>\n // Optional: full admin nav API to refresh sidebar client-side\n adminNavApi?: string\n version?: string\n settingsSectionTitle?: string\n settingsPathPrefixes?: string[]\n settingsSections?: SectionNavGroup[]\n profileSections?: SectionNavGroup[]\n profileSectionTitle?: string\n profilePathPrefixes?: string[]\n mobileSidebarSlot?: React.ReactNode\n}\n\ntype Breadcrumb = Array<{ label: string; href?: string }>\n\ntype SidebarCustomizationDraft = {\n order: string[]\n groupLabels: Record<string, string>\n itemLabels: Record<string, string>\n hiddenItemIds: Record<string, boolean>\n}\n\ntype SidebarGroup = AppShellProps['groups'][number]\ntype SidebarItem = SidebarGroup['items'][number]\ntype SidebarRoleTarget = { id: string; name: string; hasPreference: boolean }\n\nfunction resolveGroupKey(group: SidebarGroup): string {\n if (group.id && group.id.length) return group.id\n if (group.defaultName && group.defaultName.length) return slugifySidebarId(group.defaultName)\n return slugifySidebarId(group.name)\n}\n\nconst HeaderContext = React.createContext<{\n setBreadcrumb: (b?: Breadcrumb) => void\n setTitle: (t?: string) => void\n} | null>(null)\n\nexport function ApplyBreadcrumb({ breadcrumb, title, titleKey }: { breadcrumb?: Array<{ label: string; href?: string; labelKey?: string }>; title?: string; titleKey?: string }) {\n const ctx = React.useContext(HeaderContext)\n const t = useT()\n const resolvedBreadcrumb = React.useMemo<Breadcrumb | undefined>(() => {\n if (!breadcrumb) return undefined\n return breadcrumb.map(({ label, labelKey, href }) => {\n const translated = labelKey ? t(labelKey) : undefined\n const finalLabel = translated && translated !== labelKey ? translated : label\n return {\n href,\n label: finalLabel,\n }\n })\n }, [breadcrumb, t])\n const resolvedTitle = React.useMemo(() => {\n if (!titleKey) return title\n const translated = t(titleKey)\n if (translated && translated !== titleKey) return translated\n return title\n }, [titleKey, title, t])\n React.useEffect(() => {\n ctx?.setBreadcrumb(resolvedBreadcrumb)\n if (resolvedTitle !== undefined) ctx?.setTitle(resolvedTitle)\n }, [ctx, resolvedBreadcrumb, resolvedTitle])\n return null\n}\n\nconst DefaultIcon = (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\">\n <path d=\"M8 6h13M8 12h13M8 18h13\"/>\n <path d=\"M3 6h.01M3 12h.01M3 18h.01\"/>\n </svg>\n)\n\n// DataTable icon used for dynamic custom entity records links\nconst DataTableIcon = (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\">\n <rect x=\"3\" y=\"4\" width=\"18\" height=\"16\" rx=\"2\" ry=\"2\"/>\n <line x1=\"3\" y1=\"8\" x2=\"21\" y2=\"8\"/>\n <line x1=\"9\" y1=\"8\" x2=\"9\" y2=\"20\"/>\n <line x1=\"15\" y1=\"8\" x2=\"15\" y2=\"20\"/>\n </svg>\n)\n\nconst CustomizeIcon = (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\">\n <circle cx=\"12\" cy=\"12\" r=\"3\" />\n <path d=\"M19.4 15a1.65 1.65 0 0 0 .33 1.82l.05.05a2 2 0 1 1-2.83 2.83l-.05-.05A1.65 1.65 0 0 0 15 19.4a1.65 1.65 0 0 0-1 .6 1.65 1.65 0 0 0-.33 1.82l-.05.05a2 2 0 1 1-2.83-2.83l.05-.05A1.65 1.65 0 0 0 9 15a1.65 1.65 0 0 0-1-.6 1.65 1.65 0 0 0-1.82.33l-.05.05a2 2 0 1 1-2.83-2.83l.05-.05A1.65 1.65 0 0 0 4.6 9 1.65 1.65 0 0 0 4 8a1.65 1.65 0 0 0-.6-1.82l-.05-.05a2 2 0 1 1 2.83-2.83l.05.05A1.65 1.65 0 0 0 9 4.6a1.65 1.65 0 0 0 1-.6 1.65 1.65 0 0 0 .33-1.82l.05-.05a2 2 0 1 1 2.83 2.83l-.05.05A1.65 1.65 0 0 0 15 9a1.65 1.65 0 0 0 1 .6 1.65 1.65 0 0 0 1.82-.33l.05-.05a2 2 0 1 1 2.83 2.83l-.05.05A1.65 1.65 0 0 0 19.4 15z\" />\n </svg>\n)\n\nconst BackArrowIcon = (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <path d=\"M19 12H5M12 19l-7-7 7-7\" />\n </svg>\n)\n\nfunction Chevron({ open }: { open: boolean }) {\n return (\n <svg className={`transition-transform ${open ? 'rotate-180' : ''}`} width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\"><path d=\"M6 9l6 6 6-6\"/></svg>\n )\n}\n\nexport function AppShell({ productName, email, groups, rightHeaderSlot, children, sidebarCollapsedDefault = false, currentTitle, breadcrumb, adminNavApi, version, settingsSectionTitle, settingsPathPrefixes = [], settingsSections, profileSections, profileSectionTitle, profilePathPrefixes = [], mobileSidebarSlot }: AppShellProps) {\n const pathname = usePathname()\n const searchParams = useSearchParams()\n const t = useT()\n const locale = useLocale()\n const resolvedProductName = productName ?? t('appShell.productName')\n const [mobileOpen, setMobileOpen] = React.useState(false)\n // Initialize from server-provided prop only to avoid hydration flicker\n const [collapsed, setCollapsed] = React.useState(sidebarCollapsedDefault)\n // Maintain internal nav state so we can augment it client-side\n const [navGroups, setNavGroups] = React.useState(AppShell.cloneGroups(groups))\n const [openGroups, setOpenGroups] = React.useState<Record<string, boolean>>(() =>\n Object.fromEntries(groups.map((g) => [resolveGroupKey(g), true])) as Record<string, boolean>\n )\n const [customizing, setCustomizing] = React.useState(false)\n const [customDraft, setCustomDraft] = React.useState<SidebarCustomizationDraft | null>(null)\n const [loadingPreferences, setLoadingPreferences] = React.useState(false)\n const [savingPreferences, setSavingPreferences] = React.useState(false)\n const [customizationError, setCustomizationError] = React.useState<string | null>(null)\n const [availableRoleTargets, setAvailableRoleTargets] = React.useState<SidebarRoleTarget[]>([])\n const [selectedRoleIds, setSelectedRoleIds] = React.useState<string[]>([])\n const [canApplyToRoles, setCanApplyToRoles] = React.useState(false)\n const originalNavRef = React.useRef<SidebarGroup[] | null>(null)\n const [headerTitle, setHeaderTitle] = React.useState<string | undefined>(currentTitle)\n const [headerBreadcrumb, setHeaderBreadcrumb] = React.useState<Breadcrumb | undefined>(breadcrumb)\n const effectiveCollapsed = customizing ? false : collapsed\n const expandedSidebarWidth = customizing ? '320px' : '240px'\n const injectionContext = React.useMemo(\n () => ({\n path: pathname ?? '',\n query: searchParams?.toString() ?? '',\n }),\n [pathname, searchParams],\n )\n\n const isOnSettingsPath = React.useMemo(() => {\n if (!pathname) return false\n if (pathname === '/backend/settings') return true\n return settingsPathPrefixes.some((prefix) => pathname.startsWith(prefix))\n }, [pathname, settingsPathPrefixes])\n\n const isOnProfilePath = React.useMemo(() => {\n if (!pathname) return false\n if (pathname === '/backend/profile') return true\n return profilePathPrefixes.some((prefix) => pathname.startsWith(prefix))\n }, [pathname, profilePathPrefixes])\n\n const sidebarMode: 'main' | 'settings' | 'profile' =\n isOnSettingsPath ? 'settings' :\n isOnProfilePath ? 'profile' :\n 'main'\n\n // Lock body scroll when mobile drawer is open so touch scroll stays in the drawer\n React.useEffect(() => {\n if (!mobileOpen || typeof document === 'undefined') return\n const prev = document.body.style.overflow\n document.body.style.overflow = 'hidden'\n return () => {\n document.body.style.overflow = prev\n }\n }, [mobileOpen])\n\n React.useEffect(() => {\n try {\n const savedOpen = typeof window !== 'undefined' ? localStorage.getItem('om:sidebarOpenGroups') : null\n if (!savedOpen) return\n const parsed = JSON.parse(savedOpen) as Record<string, boolean>\n setOpenGroups((prev) => {\n const next = { ...prev }\n for (const group of groups) {\n const key = resolveGroupKey(group)\n if (key in parsed) next[key] = !!parsed[key]\n else if (group.name in parsed) next[key] = !!parsed[group.name]\n }\n return next\n })\n } catch {\n // ignore localStorage errors to avoid breaking hydration\n }\n }, [groups])\n\n const toggleGroup = (groupId: string) => setOpenGroups((prev) => ({ ...prev, [groupId]: prev[groupId] === false }))\n\n const updateDraft = React.useCallback((updater: (draft: SidebarCustomizationDraft) => SidebarCustomizationDraft) => {\n setCustomDraft((prev) => {\n if (!prev) return prev\n const next = updater(prev)\n if (originalNavRef.current) {\n setNavGroups(applyCustomizationDraft(originalNavRef.current, next))\n }\n return next\n })\n }, [])\n\n const startCustomization = React.useCallback(async () => {\n if (customizing || loadingPreferences) return\n setCustomizationError(null)\n setLoadingPreferences(true)\n try {\n const baseSnapshot = filterMainSidebarGroups(AppShell.cloneGroups(navGroups))\n const call = await apiCall<{\n settings?: Record<string, unknown>\n canApplyToRoles?: boolean\n roles?: Array<{ id?: string; name?: string; hasPreference?: boolean }>\n }>('/api/auth/sidebar/preferences')\n const data = call.ok ? (call.result ?? null) : null\n const rawSettings = data?.settings\n const responseOrder = Array.isArray(rawSettings?.groupOrder)\n ? rawSettings.groupOrder\n .map((id: unknown) => (typeof id === 'string' ? id.trim() : ''))\n .filter((id: string) => id.length > 0)\n : []\n const responseGroupLabels: Record<string, string> = {}\n if (rawSettings?.groupLabels && typeof rawSettings.groupLabels === 'object') {\n for (const [key, value] of Object.entries(rawSettings.groupLabels as Record<string, unknown>)) {\n if (typeof value !== 'string') continue\n const trimmedKey = key.trim()\n if (!trimmedKey) continue\n responseGroupLabels[trimmedKey] = value\n }\n }\n const responseItemLabels: Record<string, string> = {}\n if (rawSettings?.itemLabels && typeof rawSettings.itemLabels === 'object') {\n for (const [key, value] of Object.entries(rawSettings.itemLabels as Record<string, unknown>)) {\n if (typeof value !== 'string') continue\n const trimmedKey = key.trim()\n if (!trimmedKey) continue\n responseItemLabels[trimmedKey] = value\n }\n }\n const responseHiddenItems = Array.isArray(rawSettings?.hiddenItems)\n ? rawSettings.hiddenItems\n .map((href: unknown) => (typeof href === 'string' ? href.trim() : ''))\n .filter((href: string) => href.length > 0)\n : []\n const canManageRoles = data?.canApplyToRoles === true\n setCanApplyToRoles(canManageRoles)\n if (canManageRoles) {\n const roles = Array.isArray(data?.roles)\n ? (data.roles as Array<{ id?: string; name?: string; hasPreference?: boolean }>).filter((role) => typeof role?.id === 'string' && typeof role?.name === 'string')\n : []\n const mappedRoles: SidebarRoleTarget[] = roles.map((role) => ({\n id: role.id as string,\n name: role.name as string,\n hasPreference: role.hasPreference === true,\n }))\n setAvailableRoleTargets(mappedRoles)\n setSelectedRoleIds(mappedRoles.filter((role) => role.hasPreference).map((role) => role.id))\n } else {\n setAvailableRoleTargets([])\n setSelectedRoleIds([])\n }\n const currentIds = baseSnapshot.map((group) => resolveGroupKey(group))\n const order = mergeGroupOrder(responseOrder, currentIds)\n const { itemDefaults } = collectSidebarDefaults(baseSnapshot)\n const hiddenItemIds: Record<string, boolean> = {}\n for (const href of responseHiddenItems) {\n if (!itemDefaults.has(href)) continue\n hiddenItemIds[href] = true\n }\n const draft: SidebarCustomizationDraft = {\n order,\n groupLabels: { ...responseGroupLabels },\n itemLabels: { ...responseItemLabels },\n hiddenItemIds,\n }\n originalNavRef.current = baseSnapshot\n setCustomDraft(draft)\n setNavGroups(applyCustomizationDraft(baseSnapshot, draft))\n setCustomizing(true)\n } catch (error) {\n console.error('Failed to load sidebar preferences', error)\n setCustomizationError(t('appShell.sidebarCustomizationLoadError'))\n } finally {\n setLoadingPreferences(false)\n }\n }, [customizing, loadingPreferences, navGroups, t])\n\n const cancelCustomization = React.useCallback(() => {\n setCustomizing(false)\n setCustomDraft(null)\n setCustomizationError(null)\n setAvailableRoleTargets([])\n setSelectedRoleIds([])\n setCanApplyToRoles(false)\n if (originalNavRef.current) {\n setNavGroups(AppShell.cloneGroups(originalNavRef.current))\n }\n originalNavRef.current = null\n }, [])\n\n const resetCustomization = React.useCallback(() => {\n if (!originalNavRef.current) return\n const base = AppShell.cloneGroups(originalNavRef.current)\n const order = base.map((group) => resolveGroupKey(group))\n const draft: SidebarCustomizationDraft = { order, groupLabels: {}, itemLabels: {}, hiddenItemIds: {} }\n originalNavRef.current = base\n setCustomDraft(draft)\n setNavGroups(applyCustomizationDraft(base, draft))\n if (canApplyToRoles) {\n setSelectedRoleIds(availableRoleTargets.filter((role) => role.hasPreference).map((role) => role.id))\n }\n }, [availableRoleTargets, canApplyToRoles])\n\n const saveCustomization = React.useCallback(async () => {\n if (!customDraft) return\n setSavingPreferences(true)\n setCustomizationError(null)\n try {\n const baseGroups = originalNavRef.current ?? filterMainSidebarGroups(AppShell.cloneGroups(navGroups))\n const { groupDefaults, itemDefaults } = collectSidebarDefaults(baseGroups)\n const sanitizedGroupLabels: Record<string, string> = {}\n for (const [key, value] of Object.entries(customDraft.groupLabels)) {\n const trimmed = value.trim()\n const base = groupDefaults.get(key)\n if (!trimmed || !base) continue\n if (trimmed !== base) sanitizedGroupLabels[key] = trimmed\n }\n const sanitizedItemLabels: Record<string, string> = {}\n for (const [href, value] of Object.entries(customDraft.itemLabels)) {\n const trimmed = value.trim()\n const base = itemDefaults.get(href)\n if (!trimmed || !base) continue\n if (trimmed !== base) sanitizedItemLabels[href] = trimmed\n }\n const sanitizedHiddenItems: string[] = []\n for (const [href, hidden] of Object.entries(customDraft.hiddenItemIds)) {\n if (!hidden) continue\n if (!itemDefaults.has(href)) continue\n sanitizedHiddenItems.push(href)\n }\n const applyToRolesPayload = canApplyToRoles ? [...selectedRoleIds] : []\n const clearRoleIdsPayload = canApplyToRoles\n ? availableRoleTargets\n .filter((role) => role.hasPreference && !selectedRoleIds.includes(role.id))\n .map((role) => role.id)\n : []\n const payload: Record<string, unknown> = {\n groupOrder: customDraft.order,\n groupLabels: sanitizedGroupLabels,\n itemLabels: sanitizedItemLabels,\n hiddenItems: sanitizedHiddenItems,\n }\n if (canApplyToRoles) {\n payload.applyToRoles = applyToRolesPayload\n payload.clearRoleIds = clearRoleIdsPayload\n }\n const call = await apiCall<{\n canApplyToRoles?: boolean\n roles?: Array<{ id?: string; name?: string; hasPreference?: boolean }>\n }>('/api/auth/sidebar/preferences', {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n })\n if (!call.ok) {\n setCustomizationError(t('appShell.sidebarCustomizationSaveError'))\n return\n }\n const data = call.result ?? null\n if (data?.canApplyToRoles !== undefined) {\n setCanApplyToRoles(data.canApplyToRoles === true)\n }\n if (Array.isArray(data?.roles)) {\n const mappedRoles: SidebarRoleTarget[] = (data.roles as Array<{ id?: string; name?: string; hasPreference?: boolean }>).filter((role) => typeof role?.id === 'string' && typeof role?.name === 'string').map((role) => ({\n id: role.id as string,\n name: role.name as string,\n hasPreference: role.hasPreference === true,\n }))\n setAvailableRoleTargets(mappedRoles)\n setSelectedRoleIds(mappedRoles.filter((role) => role.hasPreference).map((role) => role.id))\n }\n originalNavRef.current = applyCustomizationDraft(baseGroups, customDraft)\n setNavGroups(AppShell.cloneGroups(originalNavRef.current))\n setCustomizing(false)\n setCustomDraft(null)\n try { window.dispatchEvent(new Event('om:refresh-sidebar')) } catch {}\n } catch (error) {\n console.error('Failed to save sidebar preferences', error)\n setCustomizationError(t('appShell.sidebarCustomizationSaveError'))\n } finally {\n setSavingPreferences(false)\n }\n }, [customDraft, navGroups, t])\n\n const moveGroup = React.useCallback((groupId: string, offset: number) => {\n updateDraft((draft) => {\n const order = [...draft.order]\n const index = order.indexOf(groupId)\n if (index === -1) return draft\n const nextIndex = Math.max(0, Math.min(order.length - 1, index + offset))\n if (nextIndex === index) return draft\n order.splice(index, 1)\n order.splice(nextIndex, 0, groupId)\n return { ...draft, order }\n })\n }, [updateDraft])\n\n const setGroupLabel = React.useCallback((groupId: string, value: string) => {\n updateDraft((draft) => {\n const next = { ...draft.groupLabels }\n if (value.trim().length === 0) delete next[groupId]\n else next[groupId] = value\n return { ...draft, groupLabels: next }\n })\n }, [updateDraft])\n\n const setItemLabel = React.useCallback((href: string, value: string) => {\n updateDraft((draft) => {\n const next = { ...draft.itemLabels }\n if (value.trim().length === 0) delete next[href]\n else next[href] = value\n return { ...draft, itemLabels: next }\n })\n }, [updateDraft])\n const setItemHidden = React.useCallback((href: string, hidden: boolean) => {\n updateDraft((draft) => {\n const next = { ...draft.hiddenItemIds }\n if (hidden) next[href] = true\n else delete next[href]\n return { ...draft, hiddenItemIds: next }\n })\n }, [updateDraft])\n\n const toggleRoleSelection = React.useCallback((roleId: string) => {\n setSelectedRoleIds((prev) => (prev.includes(roleId) ? prev.filter((id) => id !== roleId) : [...prev, roleId]))\n }, [])\n\n const asideWidth = effectiveCollapsed ? '72px' : expandedSidebarWidth\n // Use min-h-svh so the border extends with tall content; keep overflow for long menus\n const asideClassesBase = `border-r bg-background/60 py-4 min-h-svh overflow-y-auto`;\n\n // Persist collapse state to localStorage and cookie\n React.useEffect(() => {\n try { localStorage.setItem('om:sidebarCollapsed', collapsed ? '1' : '0') } catch {}\n try {\n document.cookie = `om_sidebar_collapsed=${collapsed ? '1' : '0'}; path=/; max-age=31536000; samesite=lax`\n } catch {}\n }, [collapsed])\n React.useEffect(() => {\n try { localStorage.setItem('om:sidebarOpenGroups', JSON.stringify(openGroups)) } catch {}\n }, [openGroups])\n\n // Ensure current route's group is expanded on load\n React.useEffect(() => {\n const activeGroup = navGroups.find((g) => g.items.some((i) => pathname?.startsWith(i.href)))\n if (!activeGroup) return\n const key = resolveGroupKey(activeGroup)\n setOpenGroups((prev) => (prev[key] === false ? { ...prev, [key]: true } : prev))\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [pathname, navGroups])\n // Keep header state in sync with props (server-side updates)\n React.useEffect(() => {\n setHeaderTitle(currentTitle)\n setHeaderBreadcrumb(breadcrumb)\n }, [currentTitle, breadcrumb])\n\n // Keep navGroups in sync when server-provided groups change\n React.useEffect(() => {\n if (customizing && customDraft && originalNavRef.current) {\n originalNavRef.current = filterMainSidebarGroups(AppShell.cloneGroups(groups))\n setNavGroups(applyCustomizationDraft(originalNavRef.current, customDraft))\n return\n }\n setNavGroups(AppShell.cloneGroups(groups))\n }, [groups, customizing, customDraft])\n\n // Optional: full refresh from adminNavApi, used to reflect RBAC/org/entity changes without page reload\n React.useEffect(() => {\n let cancelled = false\n function indexIcons(groupsToIndex: AppShellProps['groups']): Map<string, React.ReactNode | undefined> {\n const map = new Map<string, React.ReactNode | undefined>()\n for (const g of groupsToIndex) {\n for (const i of g.items) {\n map.set(i.href, i.icon)\n if (i.children) for (const c of i.children) map.set(c.href, c.icon)\n }\n }\n return map\n }\n function mergePreservingIcons(oldG: AppShellProps['groups'], newG: AppShellProps['groups']): AppShellProps['groups'] {\n const iconMap = indexIcons(oldG)\n const merged = newG.map((g) => ({\n id: g.id,\n name: g.name,\n defaultName: g.defaultName,\n items: g.items.map((i) => ({\n href: i.href,\n title: i.title,\n defaultTitle: i.defaultTitle,\n enabled: i.enabled,\n hidden: i.hidden,\n icon: i.icon ?? iconMap.get(i.href),\n pageContext: i.pageContext,\n children: i.children?.map((c) => ({\n href: c.href,\n title: c.title,\n defaultTitle: c.defaultTitle,\n enabled: c.enabled,\n hidden: c.hidden,\n icon: c.icon ?? iconMap.get(c.href),\n pageContext: c.pageContext,\n })),\n })),\n }))\n return merged\n }\n async function refreshFullNav() {\n if (!adminNavApi) return\n try {\n const call = await apiCall<{ groups?: unknown[] }>(adminNavApi, { credentials: 'include' as any })\n if (!call.ok) return\n const data = call.result\n if (cancelled) return\n const nextGroups = Array.isArray(data?.groups) ? data.groups : []\n if (nextGroups.length) setNavGroups((prev) => AppShell.cloneGroups(mergePreservingIcons(prev, nextGroups as any)))\n } catch {}\n }\n // Refresh on window focus\n const onFocus = () => refreshFullNav()\n window.addEventListener('focus', onFocus)\n return () => { cancelled = true; window.removeEventListener('focus', onFocus) }\n }, [adminNavApi])\n\n // Refresh sidebar when other parts of the app dispatch an explicit event\n React.useEffect(() => {\n if (!adminNavApi) return\n const api = adminNavApi as string\n let cancelled = false\n function indexIcons(groupsToIndex: AppShellProps['groups']): Map<string, React.ReactNode | undefined> {\n const map = new Map<string, React.ReactNode | undefined>()\n for (const g of groupsToIndex) {\n for (const i of g.items) {\n map.set(i.href, i.icon)\n if (i.children) for (const c of i.children) map.set(c.href, c.icon)\n }\n }\n return map\n }\n function mergePreservingIcons(oldG: AppShellProps['groups'], newG: AppShellProps['groups']): AppShellProps['groups'] {\n const iconMap = indexIcons(oldG)\n const merged = newG.map((g) => ({\n id: g.id,\n name: g.name,\n defaultName: g.defaultName,\n items: g.items.map((i) => ({\n href: i.href,\n title: i.title,\n defaultTitle: i.defaultTitle,\n enabled: i.enabled,\n hidden: i.hidden,\n icon: i.icon ?? iconMap.get(i.href),\n pageContext: i.pageContext,\n children: i.children?.map((c) => ({\n href: c.href,\n title: c.title,\n defaultTitle: c.defaultTitle,\n enabled: c.enabled,\n hidden: c.hidden,\n icon: c.icon ?? iconMap.get(c.href),\n pageContext: c.pageContext,\n })),\n })),\n }))\n return merged\n }\n async function refreshFullNav() {\n try {\n const call = await apiCall<{ groups?: unknown[] }>(api, { credentials: 'include' as any })\n if (!call.ok) return\n const data = call.result\n if (cancelled) return\n const nextGroups = Array.isArray(data?.groups) ? data.groups : []\n if (nextGroups.length) setNavGroups((prev) => AppShell.cloneGroups(mergePreservingIcons(prev, nextGroups as any)))\n } catch {}\n }\n const onRefresh = () => { refreshFullNav() }\n window.addEventListener('om:refresh-sidebar', onRefresh as any)\n return () => { cancelled = true; window.removeEventListener('om:refresh-sidebar', onRefresh as any) }\n }, [adminNavApi])\n\n // adminNavApi already includes user entities; no extra fetch\n\n function renderSectionSidebar(\n sections: SectionNavGroup[],\n title: string,\n compact: boolean,\n hideHeader?: boolean\n ) {\n const sortedSections = [...sections].sort((a, b) => (a.order ?? 0) - (b.order ?? 0))\n const lastVisibleIndex = sortedSections.length - 1\n\n return (\n <div className=\"flex flex-col min-h-full gap-3\">\n {!hideHeader && (\n <div className={`flex items-center ${compact ? 'justify-center' : 'justify-between'} mb-2`}>\n <Link href=\"/backend\" className=\"flex items-center gap-2\" aria-label={t('appShell.goToDashboard')}>\n <Image src=\"/open-mercato.svg\" alt={resolvedProductName} width={32} height={32} className=\"rounded m-4\" />\n {!compact && <div className=\"text-m font-semibold\">{resolvedProductName}</div>}\n </Link>\n </div>\n )}\n <div className=\"flex flex-1 flex-col gap-3 overflow-y-auto pr-1\">\n <Link\n href=\"/backend\"\n className={`flex items-center gap-2 ${compact ? 'justify-center px-2' : 'px-2'} py-1 text-sm text-muted-foreground hover:text-foreground transition-colors`}\n aria-label={t('backend.nav.backToMain', 'Back')}\n >\n <span className=\"flex items-center justify-center shrink-0\">{BackArrowIcon}</span>\n {!compact && <span>{title}</span>}\n </Link>\n <nav className=\"flex flex-col gap-2\">\n {sortedSections.map((section, sectionIndex) => {\n const visibleItems = section.items\n if (visibleItems.length === 0) return null\n const sortedItems = [...visibleItems].sort((a, b) => (a.order ?? 0) - (b.order ?? 0))\n const sectionLabel = section.labelKey ? t(section.labelKey, section.label) : section.label\n const sectionKey = `settings:${section.id}`\n const open = openGroups[sectionKey] !== false\n\n return (\n <div key={section.id}>\n <Button\n variant=\"muted\"\n onClick={() => toggleGroup(sectionKey)}\n className={`w-full ${compact ? 'px-0 justify-center' : 'px-2 justify-between'} flex text-xs uppercase text-muted-foreground/90 py-2`}\n aria-expanded={open}\n >\n {!compact && <span>{sectionLabel}</span>}\n {!compact && <Chevron open={open} />}\n </Button>\n {open && (\n <div className={`flex flex-col ${compact ? 'items-center' : ''} gap-1 ${!compact ? 'pl-1' : ''}`}>\n {sortedItems.map((item) => {\n const isActive = pathname === item.href || pathname?.startsWith(item.href + '/')\n const label = item.labelKey ? t(item.labelKey, item.label) : item.label\n const base = compact ? 'w-10 h-10 justify-center' : 'px-2 py-1 gap-2'\n\n return (\n <Link\n key={item.id}\n href={item.href}\n className={`relative text-sm rounded inline-flex items-center ${base} ${\n isActive\n ? 'bg-background border shadow-sm'\n : 'hover:bg-accent hover:text-accent-foreground'\n }`}\n title={compact ? label : undefined}\n onClick={() => setMobileOpen(false)}\n >\n {isActive && (\n <span className=\"absolute left-0 top-1 bottom-1 w-0.5 rounded bg-foreground\" />\n )}\n <span className={`flex items-center justify-center shrink-0 ${compact ? '' : 'text-muted-foreground'}`}>\n {item.icon ?? DefaultIcon}\n </span>\n {!compact && <span className=\"truncate\">{label}</span>}\n </Link>\n )\n })}\n </div>\n )}\n {sectionIndex !== lastVisibleIndex && <div className=\"my-2 border-t border-dotted\" />}\n </div>\n )\n })}\n </nav>\n </div>\n </div>\n )\n }\n\n function renderSidebar(compact: boolean, hideHeader?: boolean) {\n if (sidebarMode === 'settings' && settingsSections && settingsSections.length > 0) {\n return renderSectionSidebar(\n settingsSections,\n settingsSectionTitle ?? t('backend.nav.settings', 'Settings'),\n compact,\n hideHeader\n )\n }\n\n if (sidebarMode === 'profile' && profileSections && profileSections.length > 0) {\n return renderSectionSidebar(\n profileSections,\n profileSectionTitle ?? t('backend.nav.profile', 'Profile'),\n compact,\n hideHeader\n )\n }\n\n const isMobileVariant = !!hideHeader\n const shouldRenderSidebarInjectionSpots = !isMobileVariant\n const baseGroupsForDefaults = originalNavRef.current ?? navGroups\n const baseGroupMap = new Map<string, SidebarGroup>()\n for (const group of baseGroupsForDefaults) {\n baseGroupMap.set(resolveGroupKey(group), group)\n }\n const localeLabel = (locale || '').toUpperCase()\n\n const orderedGroupIds = customDraft\n ? mergeGroupOrder(customDraft.order, Array.from(baseGroupMap.keys()))\n : navGroups.map((group) => resolveGroupKey(group))\n\n const lastVisibleGroupIndex = (() => {\n for (let idx = navGroups.length - 1; idx >= 0; idx -= 1) {\n if (navGroups[idx].items.some((item) => item.hidden !== true)) return idx\n }\n return -1\n })()\n\n const renderEditableItems = (baseItems: SidebarItem[], currentItems: SidebarItem[], depth = 0): React.ReactNode => {\n if (!customDraft) return null\n return baseItems.map((baseItem) => {\n const current = currentItems.find((item) => item.href === baseItem.href) ?? baseItem\n const placeholder = baseItem.defaultTitle ?? baseItem.title\n const value = customDraft.itemLabels[baseItem.href] ?? ''\n const hidden = customDraft.hiddenItemIds[baseItem.href] === true\n return (\n <div\n key={baseItem.href}\n className={`flex flex-col gap-1 ${hidden ? 'opacity-60' : ''}`}\n style={depth ? { marginLeft: depth * 16 } : undefined}\n >\n <span className=\"text-xs font-medium text-muted-foreground\">{placeholder}</span>\n <div className=\"flex items-center gap-2\">\n <input\n type=\"checkbox\"\n className=\"h-4 w-4 accent-foreground\"\n checked={!hidden}\n onChange={(event) => setItemHidden(baseItem.href, !event.target.checked)}\n disabled={savingPreferences}\n aria-label={t('appShell.sidebarCustomizationShowItem')}\n title={t('appShell.sidebarCustomizationShowItem')}\n />\n <input\n value={value}\n onChange={(event) => setItemLabel(baseItem.href, event.target.value)}\n placeholder={placeholder}\n disabled={savingPreferences}\n className=\"h-8 flex-1 rounded border bg-background px-2 text-sm shadow-sm focus:outline-none focus:ring-2 focus:ring-ring disabled:opacity-60\"\n />\n </div>\n {baseItem.children && baseItem.children.length > 0 ? (\n <div className=\"flex flex-col gap-1\">\n {renderEditableItems(baseItem.children, current.children ?? [], depth + 1)}\n </div>\n ) : null}\n </div>\n )\n })\n }\n\n const customizationEditor = customizing ? (\n customDraft ? (\n <div className=\"flex flex-col gap-3 rounded border border-dashed bg-muted/20 p-3\">\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <div className=\"text-sm font-semibold\">{t('appShell.sidebarCustomizationHeading')}</div>\n <div className=\"flex items-center gap-2\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={resetCustomization}\n disabled={savingPreferences}\n >\n {t('appShell.sidebarCustomizationReset')}\n </Button>\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={cancelCustomization}\n disabled={savingPreferences}\n >\n {t('appShell.sidebarCustomizationCancel')}\n </Button>\n <Button\n size=\"sm\"\n className=\"bg-foreground text-background hover:bg-foreground/90\"\n onClick={saveCustomization}\n disabled={savingPreferences}\n >\n {savingPreferences ? t('appShell.sidebarCustomizationSaving') : t('appShell.sidebarCustomizationSave')}\n </Button>\n </div>\n </div>\n <p className=\"text-xs text-muted-foreground\">{t('appShell.sidebarCustomizationHint', { locale: localeLabel })}</p>\n {canApplyToRoles ? (\n <div className=\"flex flex-col gap-2 rounded border bg-background/70 p-3 shadow-sm\">\n <div>\n <div className=\"text-sm font-semibold\">{t('appShell.sidebarApplyToRolesTitle')}</div>\n <p className=\"text-xs text-muted-foreground\">{t('appShell.sidebarApplyToRolesDescription')}</p>\n </div>\n {availableRoleTargets.length > 0 ? (\n <div className=\"flex flex-col gap-2\">\n {availableRoleTargets.map((role) => {\n const checked = selectedRoleIds.includes(role.id)\n const willClear = role.hasPreference && !checked\n return (\n <label key={role.id} className=\"flex items-center gap-2 rounded border bg-background px-2 py-1 text-sm shadow-sm\">\n <input\n type=\"checkbox\"\n className=\"h-4 w-4 accent-foreground\"\n checked={checked}\n onChange={() => toggleRoleSelection(role.id)}\n disabled={savingPreferences}\n />\n <span className=\"flex-1 truncate\">{role.name}</span>\n {role.hasPreference ? (\n <span className={`text-xs ${willClear ? 'text-destructive' : 'text-muted-foreground'}`}>\n {willClear ? t('appShell.sidebarRoleWillClear') : t('appShell.sidebarRoleHasPreset')}\n </span>\n ) : null}\n </label>\n )\n })}\n </div>\n ) : (\n <p className=\"text-xs text-muted-foreground\">{t('appShell.sidebarApplyToRolesEmpty')}</p>\n )}\n </div>\n ) : null}\n {customizationError ? <p className=\"text-xs text-destructive\">{customizationError}</p> : null}\n <div className=\"flex flex-col gap-3\">\n {orderedGroupIds.map((groupId, index) => {\n const baseGroup = baseGroupMap.get(groupId)\n if (!baseGroup) return null\n const currentGroup = navGroups.find((group) => resolveGroupKey(group) === groupId) ?? baseGroup\n const placeholder = baseGroup.defaultName ?? baseGroup.name\n const value = customDraft.groupLabels[groupId] ?? ''\n return (\n <div key={groupId} className=\"flex flex-col gap-3 rounded border bg-background p-3 shadow-sm\">\n <div className={`flex ${compact ? 'flex-col gap-2' : 'items-center gap-2'}`}>\n <div className=\"flex-1\">\n <span className=\"text-xs font-medium text-muted-foreground\">{t('appShell.sidebarCustomizationGroupLabel')}</span>\n <input\n value={value}\n onChange={(event) => setGroupLabel(groupId, event.target.value)}\n placeholder={placeholder}\n disabled={savingPreferences}\n className=\"mt-1 h-8 w-full rounded border bg-background px-2 text-sm shadow-sm focus:outline-none focus:ring-2 focus:ring-ring disabled:opacity-60\"\n />\n </div>\n <div className=\"flex items-center gap-1 self-start\">\n <IconButton\n variant=\"outline\"\n size=\"sm\"\n className=\"text-muted-foreground hover:text-foreground\"\n onClick={() => moveGroup(groupId, -1)}\n disabled={index === 0 || savingPreferences}\n aria-label={t('appShell.sidebarCustomizationMoveUp')}\n >\n <ChevronUp className=\"size-4\" />\n </IconButton>\n <IconButton\n variant=\"outline\"\n size=\"sm\"\n className=\"text-muted-foreground hover:text-foreground\"\n onClick={() => moveGroup(groupId, 1)}\n disabled={index === orderedGroupIds.length - 1 || savingPreferences}\n aria-label={t('appShell.sidebarCustomizationMoveDown')}\n >\n <ChevronDown className=\"size-4\" />\n </IconButton>\n </div>\n </div>\n <div className=\"flex flex-col gap-2\">\n {renderEditableItems(baseGroup.items, currentGroup.items)}\n </div>\n </div>\n )\n })}\n </div>\n </div>\n ) : (\n <div className=\"rounded border border-dashed bg-muted/20 p-3 text-sm text-muted-foreground\">\n {t('appShell.sidebarCustomizationLoading')}\n </div>\n )\n ) : null\n\n return (\n <div className=\"flex flex-col min-h-full gap-3\">\n {!hideHeader && (\n <div className={`flex items-center ${compact ? 'justify-center' : 'justify-between'} mb-2`}>\n <Link href=\"/backend\" className=\"flex items-center gap-2\" aria-label={t('appShell.goToDashboard')}>\n <Image src=\"/open-mercato.svg\" alt={resolvedProductName} width={32} height={32} className=\"rounded m-4\" />\n {!compact && <div className=\"text-m font-semibold\">{resolvedProductName}</div>}\n </Link>\n </div>\n )}\n {shouldRenderSidebarInjectionSpots ? (\n <InjectionSpot\n spotId={BACKEND_SIDEBAR_TOP_INJECTION_SPOT_ID}\n context={injectionContext}\n />\n ) : null}\n <div className=\"flex flex-1 flex-col gap-3 overflow-y-auto pr-1\">\n {customizing ? (\n customizationEditor\n ) : (\n (() => {\n const isSettingsPath = (href: string) => {\n if (href === '/backend/settings') return true\n return settingsPathPrefixes.some((prefix) => href.startsWith(prefix))\n }\n\n const isMainItem = (item: SidebarItem) => {\n if (item.pageContext && item.pageContext !== 'main') return false\n if (isSettingsPath(item.href)) return false\n return true\n }\n\n const mainGroups = navGroups.map((g) => ({\n ...g,\n items: g.items.filter((item) => isMainItem(item) && item.hidden !== true),\n })).filter((g) => g.items.length > 0)\n\n const mainLastVisibleGroupIndex = (() => {\n for (let idx = mainGroups.length - 1; idx >= 0; idx -= 1) {\n if (mainGroups[idx].items.some((item) => item.hidden !== true)) return idx\n }\n return -1\n })()\n\n return (\n <>\n <nav className=\"flex flex-col gap-2\">\n {mainGroups.map((g, gi) => {\n const groupId = resolveGroupKey(g)\n const open = openGroups[groupId] !== false\n const visibleItems = g.items.filter((item) => item.hidden !== true)\n if (visibleItems.length === 0) return null\n return (\n <div key={groupId}>\n <Button\n variant=\"muted\"\n onClick={() => toggleGroup(groupId)}\n className={`w-full ${compact ? 'px-0 justify-center' : 'px-2 justify-between'} flex text-xs uppercase text-muted-foreground/90 py-2`}\n aria-expanded={open}\n >\n {!compact && <span>{g.name}</span>}\n {!compact && <Chevron open={open} />}\n </Button>\n {open && (\n <div className={`flex flex-col ${compact ? 'items-center' : ''} gap-1 ${!compact ? 'pl-1' : ''}`}>\n {visibleItems.map((i) => {\n const childItems = (i.children ?? []).filter((child) => child.hidden !== true)\n const showChildren = !!pathname && childItems.length > 0 && pathname.startsWith(i.href)\n const hasActiveChild = !!(pathname && childItems.some((c) => pathname.startsWith(c.href)))\n const isParentActive = (pathname === i.href) || (showChildren && !hasActiveChild)\n const base = compact ? 'w-10 h-10 justify-center' : 'px-2 py-1 gap-2'\n return (\n <React.Fragment key={i.href}>\n <Link\n href={i.href}\n className={`relative text-sm rounded inline-flex items-center ${base} ${\n isParentActive ? 'bg-background border shadow-sm' : 'hover:bg-accent hover:text-accent-foreground'\n } ${i.enabled === false ? 'pointer-events-none opacity-50' : ''}`}\n aria-disabled={i.enabled === false}\n title={compact ? i.title : undefined}\n onClick={() => setMobileOpen(false)}\n >\n {isParentActive ? (\n <span className=\"absolute left-0 top-1 bottom-1 w-0.5 rounded bg-foreground\" />\n ) : null}\n <span className={`flex items-center justify-center shrink-0 ${compact ? '' : 'text-muted-foreground'}`}>\n {i.icon ?? DefaultIcon}\n </span>\n {!compact && <span>{i.title}</span>}\n </Link>\n {showChildren ? (\n <div className={`flex flex-col ${compact ? 'items-center' : ''} gap-1 ${!compact ? 'pl-4' : ''}`}>\n {childItems.map((c) => {\n const childActive = pathname?.startsWith(c.href)\n const childBase = compact ? 'w-10 h-8 justify-center' : 'px-2 py-1 gap-2'\n return (\n <Link\n key={c.href}\n href={c.href}\n className={`relative text-sm rounded inline-flex items-center ${childBase} ${\n childActive ? 'bg-background border shadow-sm' : 'hover:bg-accent hover:text-accent-foreground'\n } ${c.enabled === false ? 'pointer-events-none opacity-50' : ''}`}\n aria-disabled={c.enabled === false}\n title={compact ? c.title : undefined}\n onClick={() => setMobileOpen(false)}\n >\n {childActive ? (\n <span className=\"absolute left-0 top-1 bottom-1 w-0.5 rounded bg-foreground\" />\n ) : null}\n <span className={`flex items-center justify-center shrink-0 ${compact ? '' : 'text-muted-foreground'}`}>\n {c.icon ?? (c.href.includes('/backend/entities/user/') && c.href.endsWith('/records') ? DataTableIcon : DefaultIcon)}\n </span>\n {!compact && <span>{c.title}</span>}\n </Link>\n )\n })}\n </div>\n ) : null}\n </React.Fragment>\n )\n })}\n </div>\n )}\n {gi !== mainLastVisibleGroupIndex && <div className=\"my-2 border-t border-dotted\" />}\n </div>\n )\n })}\n </nav>\n <div className=\"mt-4 pt-4 border-t\">\n <Link\n href=\"/backend/settings\"\n className={`relative text-sm rounded inline-flex items-center w-full ${\n compact ? 'w-10 h-10 justify-center' : 'px-2 py-1 gap-2'\n } ${\n pathname?.startsWith('/backend/settings') || pathname?.startsWith('/backend/config') || pathname?.startsWith('/backend/users') || pathname?.startsWith('/backend/roles') || pathname?.startsWith('/backend/api-keys') || pathname?.startsWith('/backend/entities') || pathname?.startsWith('/backend/query-indexes') || pathname?.startsWith('/backend/definitions') || pathname?.startsWith('/backend/instances') || pathname?.startsWith('/backend/tasks') || pathname?.startsWith('/backend/events') || pathname?.startsWith('/backend/rules') || pathname?.startsWith('/backend/sets') || pathname?.startsWith('/backend/logs') || pathname?.startsWith('/backend/directory') || pathname?.startsWith('/backend/feature-toggles')\n ? 'bg-background border shadow-sm font-medium'\n : 'hover:bg-accent hover:text-accent-foreground'\n }`}\n title={compact ? t('backend.nav.settings', 'Settings') : undefined}\n onClick={() => setMobileOpen(false)}\n >\n {(pathname?.startsWith('/backend/settings') || pathname?.startsWith('/backend/config') || pathname?.startsWith('/backend/users') || pathname?.startsWith('/backend/roles') || pathname?.startsWith('/backend/api-keys') || pathname?.startsWith('/backend/entities') || pathname?.startsWith('/backend/query-indexes') || pathname?.startsWith('/backend/definitions') || pathname?.startsWith('/backend/instances') || pathname?.startsWith('/backend/tasks') || pathname?.startsWith('/backend/events') || pathname?.startsWith('/backend/rules') || pathname?.startsWith('/backend/sets') || pathname?.startsWith('/backend/logs') || pathname?.startsWith('/backend/directory') || pathname?.startsWith('/backend/feature-toggles')) && (\n <span className=\"absolute left-0 top-1 bottom-1 w-0.5 rounded bg-foreground\" />\n )}\n <span className={`flex items-center justify-center shrink-0 ${compact ? '' : 'text-muted-foreground'}`}>\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"3\" />\n <path d=\"M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z\" />\n </svg>\n </span>\n {!compact && <span>{t('backend.nav.settings', 'Settings')}</span>}\n </Link>\n </div>\n </>\n )\n })()\n )}\n </div>\n {!customizing && (\n <>\n {compact || isMobileVariant ? (\n <IconButton\n variant=\"outline\"\n size=\"lg\"\n className=\"mt-auto\"\n onClick={startCustomization}\n disabled={loadingPreferences}\n aria-label={t('appShell.customizeSidebar')}\n >\n {CustomizeIcon}\n </IconButton>\n ) : (\n <Button\n variant=\"outline\"\n size=\"default\"\n className=\"mt-auto\"\n onClick={startCustomization}\n disabled={loadingPreferences}\n aria-label={t('appShell.customizeSidebar')}\n >\n {CustomizeIcon}\n {loadingPreferences ? t('appShell.sidebarCustomizationLoading') : t('appShell.customizeSidebar')}\n </Button>\n )}\n </>\n )}\n {shouldRenderSidebarInjectionSpots ? (\n <InjectionSpot\n spotId={BACKEND_SIDEBAR_FOOTER_INJECTION_SPOT_ID}\n context={injectionContext}\n />\n ) : null}\n </div>\n )\n }\n\n const gridColsClass = customizing\n ? 'lg:grid-cols-[320px_1fr]'\n : (effectiveCollapsed ? 'lg:grid-cols-[72px_1fr]' : 'lg:grid-cols-[240px_1fr]')\n const headerCtxValue = React.useMemo(() => ({\n setBreadcrumb: setHeaderBreadcrumb,\n setTitle: setHeaderTitle,\n }), [])\n\n return (\n <HeaderContext.Provider value={headerCtxValue}>\n <div className={`min-h-svh lg:grid ${gridColsClass}`}>\n {/* Desktop sidebar */}\n <aside className={`${asideClassesBase} ${effectiveCollapsed ? 'px-2' : 'px-3'} hidden lg:block`} style={{ width: asideWidth }}>{renderSidebar(effectiveCollapsed)}</aside>\n\n <div className=\"flex min-h-svh flex-col min-w-0\">\n <header className=\"border-b bg-background/60 px-3 lg:px-4 py-2 lg:py-3 flex items-center justify-between gap-2\">\n <div className=\"flex items-center gap-2 min-w-0\">\n {/* Mobile menu button */}\n <IconButton variant=\"outline\" size=\"sm\" className=\"lg:hidden\" aria-label={t('appShell.openMenu')} onClick={() => setMobileOpen(true)}>\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\"><path d=\"M3 6h18M3 12h18M3 18h18\"/></svg>\n </IconButton>\n {/* Desktop collapse toggle */}\n <IconButton\n variant=\"outline\"\n size=\"sm\"\n className=\"hidden lg:inline-flex\"\n aria-label={t('appShell.toggleSidebar')}\n onClick={() => setCollapsed((c) => !c)}\n disabled={customizing}\n >\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\">\n <rect x=\"3\" y=\"4\" width=\"18\" height=\"16\" rx=\"2\"/>\n <path d=\"M9 4v16\"/>\n </svg>\n </IconButton>\n {/* Header breadcrumb: always starts with Dashboard */}\n {(() => {\n const dashboardLabel = t('dashboard.title')\n const root: Breadcrumb = [{ label: dashboardLabel, href: '/backend' }]\n let rest: Breadcrumb = []\n if (headerBreadcrumb && headerBreadcrumb.length) {\n const first = headerBreadcrumb[0]\n const dup = first && (first.href === '/backend' || first.label === dashboardLabel || first.label?.toLowerCase() === 'dashboard')\n rest = dup ? headerBreadcrumb.slice(1) : headerBreadcrumb\n } else if (headerTitle) {\n rest = [{ label: headerTitle }]\n }\n const items = [...root, ...rest]\n const lastIndex = items.length - 1\n return (\n <nav className=\"flex items-center gap-2 text-sm min-w-0\">\n {items.map((b, i) => {\n const isLast = i === lastIndex\n const hiddenOnMobile = !isLast ? 'hidden md:inline' : ''\n return (\n <React.Fragment key={i}>\n {i > 0 && <span className={`text-muted-foreground hidden md:inline`}>/</span>}\n {b.href ? (\n <Link href={b.href} className={`text-muted-foreground hover:text-foreground ${hiddenOnMobile}`}>\n {b.label}\n </Link>\n ) : (\n <span className={`font-medium truncate max-w-[45vw] md:max-w-[60vw]`}>{b.label}</span>\n )}\n </React.Fragment>\n )\n })}\n </nav>\n )\n })()}\n </div>\n <div className=\"flex items-center gap-1 md:gap-2 text-sm shrink-0\">\n {rightHeaderSlot ? (\n rightHeaderSlot\n ) : (\n <span className=\"opacity-80\">{email || t('appShell.userFallback')}</span>\n )}\n </div>\n </header>\n <main className=\"flex-1 p-4 lg:p-6\">\n <InjectionSpot spotId={BACKEND_LAYOUT_TOP_INJECTION_SPOT_ID} context={injectionContext} />\n <FlashMessages />\n <PartialIndexBanner />\n <UpgradeActionBanner />\n <LastOperationBanner />\n <InjectionSpot spotId={BACKEND_RECORD_CURRENT_INJECTION_SPOT_ID} context={injectionContext} />\n <InjectionSpot\n spotId={LEGACY_GLOBAL_MUTATION_INJECTION_SPOT_ID}\n context={injectionContext}\n />\n <div id=\"om-top-banners\" className=\"mb-3 space-y-2\" />\n {children}\n <InjectionSpot spotId={BACKEND_LAYOUT_FOOTER_INJECTION_SPOT_ID} context={injectionContext} />\n </main>\n <footer className=\"border-t bg-background/80 backdrop-blur supports-[backdrop-filter]:bg-background/50 px-4 py-3 flex flex-wrap items-center justify-end gap-4\">\n {version ? (\n <span className=\"text-xs text-muted-foreground\">\n {t('appShell.version', { version })}\n </span>\n ) : null}\n <nav className=\"flex items-center gap-3 text-xs text-muted-foreground\">\n <Link href=\"/terms\" className=\"transition hover:text-foreground\">\n {t('common.terms')}\n </Link>\n <Link href=\"/privacy\" className=\"transition hover:text-foreground\">\n {t('common.privacy')}\n </Link>\n </nav>\n </footer>\n </div>\n\n {/* Mobile drawer */}\n {mobileOpen && (\n <div className=\"lg:hidden fixed inset-0 z-50\">\n <div className=\"absolute inset-0 bg-black/40\" onClick={() => setMobileOpen(false)} />\n <aside className=\"absolute left-0 top-0 flex h-full w-[260px] flex-col bg-background border-r overflow-hidden\">\n <div className=\"shrink-0 p-3 pb-2 flex items-center justify-between border-b\">\n <Link href=\"/backend\" className=\"flex items-center gap-2 text-sm font-semibold\" onClick={() => setMobileOpen(false)} aria-label={t('appShell.goToDashboard')}>\n <Image src=\"/open-mercato.svg\" alt={resolvedProductName} width={28} height={28} className=\"rounded\" />\n {resolvedProductName}\n </Link>\n <IconButton variant=\"outline\" size=\"sm\" onClick={() => setMobileOpen(false)} aria-label={t('appShell.closeMenu')}>\u2715</IconButton>\n </div>\n {mobileSidebarSlot && (\n <div className=\"shrink-0 border-b px-3 py-2\">\n {mobileSidebarSlot}\n </div>\n )}\n <div className=\"min-h-0 flex-1 overflow-y-auto overflow-x-hidden p-3\">\n {/* Force expanded sidebar in mobile drawer, hide its header and collapse toggle */}\n {renderSidebar(false, true)}\n </div>\n </aside>\n </div>\n )}\n </div>\n </HeaderContext.Provider>\n )\n}\n\n// Helper: deep-clone minimal shape we mutate (children arrays)\nAppShell.cloneGroups = function cloneGroups(groups: AppShellProps['groups']): AppShellProps['groups'] {\n const cloneItem = (item: SidebarItem): SidebarItem => ({\n href: item.href,\n title: item.title,\n defaultTitle: item.defaultTitle,\n icon: item.icon,\n enabled: item.enabled,\n hidden: item.hidden,\n pageContext: item.pageContext,\n children: item.children ? item.children.map((child) => cloneItem(child)) : undefined,\n })\n return groups.map((group) => ({\n id: group.id,\n name: group.name,\n defaultName: group.defaultName,\n items: group.items.map((item) => cloneItem(item)),\n }))\n}\n\nfunction applyCustomizationDraft(baseGroups: SidebarGroup[], draft: SidebarCustomizationDraft): SidebarGroup[] {\n const clones = AppShell.cloneGroups(baseGroups)\n const byId = new Map<string, SidebarGroup>()\n for (const group of clones) {\n byId.set(resolveGroupKey(group), group)\n }\n const orderedIds = mergeGroupOrder(draft.order, Array.from(byId.keys()))\n const seen = new Set<string>()\n const result: SidebarGroup[] = []\n for (const id of orderedIds) {\n if (seen.has(id)) continue\n const group = byId.get(id)\n if (!group) continue\n seen.add(id)\n const baseName = group.defaultName ?? group.name\n const override = draft.groupLabels[id]?.trim()\n result.push({\n ...group,\n name: override && override.length > 0 ? override : baseName,\n items: group.items.map((item) => applyItemDraft(item, draft)),\n })\n }\n return result\n}\n\nfunction applyItemDraft(item: SidebarItem, draft: SidebarCustomizationDraft): SidebarItem {\n const baseTitle = item.defaultTitle ?? item.title\n const override = draft.itemLabels[item.href]?.trim()\n const children = item.children\n ? item.children\n .map((child) => applyItemDraft(child, draft))\n : undefined\n const hidden = draft.hiddenItemIds[item.href] === true\n return {\n ...item,\n title: override && override.length > 0 ? override : baseTitle,\n hidden,\n children,\n }\n}\n\nfunction mergeGroupOrder(preferred: string[], current: string[]): string[] {\n const seen = new Set<string>()\n const merged: string[] = []\n for (const id of preferred) {\n const trimmed = id.trim()\n if (!trimmed || seen.has(trimmed) || !current.includes(trimmed)) continue\n seen.add(trimmed)\n merged.push(trimmed)\n }\n for (const id of current) {\n if (seen.has(id)) continue\n seen.add(id)\n merged.push(id)\n }\n return merged\n}\n\nfunction collectSidebarDefaults(groups: SidebarGroup[]) {\n const groupDefaults = new Map<string, string>()\n const itemDefaults = new Map<string, string>()\n\n const visitItems = (items: SidebarItem[]) => {\n for (const item of items) {\n const baseTitle = item.defaultTitle ?? item.title\n itemDefaults.set(item.href, baseTitle)\n if (item.children && item.children.length > 0) visitItems(item.children)\n }\n }\n\n for (const group of groups) {\n const key = resolveGroupKey(group)\n groupDefaults.set(key, group.defaultName ?? group.name)\n visitItems(group.items)\n }\n\n return { groupDefaults, itemDefaults }\n}\n\n/**\n * Filters groups to include only main sidebar items.\n * Excludes items with pageContext 'settings' or 'profile' from customization.\n * Per SPEC-007: Sidebar customization applies only to the main sidebar.\n */\nfunction filterMainSidebarGroups(groups: SidebarGroup[]): SidebarGroup[] {\n const isMainItem = (item: SidebarItem): boolean => {\n if (item.pageContext && item.pageContext !== 'main') return false\n return true\n }\n\n return groups\n .map((group) => ({\n ...group,\n items: group.items.filter(isMainItem).map((item) => ({\n ...item,\n children: item.children?.filter(isMainItem),\n })),\n }))\n .filter((group) => group.items.length > 0)\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport Link from 'next/link'\nimport Image from 'next/image'\nimport { ChevronUp, ChevronDown } from 'lucide-react'\nimport { Button } from '../primitives/button'\nimport { IconButton } from '../primitives/icon-button'\nimport { Separator } from '../primitives/separator'\nimport { FlashMessages } from './FlashMessages'\nimport { usePathname, useSearchParams } from 'next/navigation'\nimport { apiCall } from './utils/apiCall'\nimport { LastOperationBanner } from './operations/LastOperationBanner'\nimport { ProgressTopBar } from './progress/ProgressTopBar'\nimport { UpgradeActionBanner } from './upgrades/UpgradeActionBanner'\nimport { PartialIndexBanner } from './indexes/PartialIndexBanner'\nimport { useLocale, useT } from '@open-mercato/shared/lib/i18n/context'\nimport { slugifySidebarId } from '@open-mercato/shared/modules/navigation/sidebarPreferences'\nimport type { SectionNavGroup } from './section-page/types'\nimport { InjectionSpot } from './injection/InjectionSpot'\nimport { LEGACY_GLOBAL_MUTATION_INJECTION_SPOT_ID } from './injection/mutationEvents'\nimport {\n BACKEND_LAYOUT_FOOTER_INJECTION_SPOT_ID,\n BACKEND_LAYOUT_TOP_INJECTION_SPOT_ID,\n BACKEND_RECORD_CURRENT_INJECTION_SPOT_ID,\n BACKEND_SIDEBAR_FOOTER_INJECTION_SPOT_ID,\n BACKEND_SIDEBAR_TOP_INJECTION_SPOT_ID,\n} from './injection/spotIds'\n\nexport type AppShellProps = {\n productName?: string\n email?: string\n groups: {\n id?: string\n name: string\n defaultName?: string\n items: {\n href: string\n title: string\n defaultTitle?: string\n icon?: React.ReactNode\n enabled?: boolean\n hidden?: boolean\n pageContext?: 'main' | 'admin' | 'settings' | 'profile'\n children?: {\n href: string\n title: string\n defaultTitle?: string\n icon?: React.ReactNode\n enabled?: boolean\n hidden?: boolean\n pageContext?: 'main' | 'admin' | 'settings' | 'profile'\n }[]\n }[]\n }[]\n children: React.ReactNode\n rightHeaderSlot?: React.ReactNode\n sidebarCollapsedDefault?: boolean\n currentTitle?: string\n breadcrumb?: Array<{ label: string; href?: string }>\n // Optional: full admin nav API to refresh sidebar client-side\n adminNavApi?: string\n version?: string\n settingsSectionTitle?: string\n settingsPathPrefixes?: string[]\n settingsSections?: SectionNavGroup[]\n profileSections?: SectionNavGroup[]\n profileSectionTitle?: string\n profilePathPrefixes?: string[]\n mobileSidebarSlot?: React.ReactNode\n}\n\ntype Breadcrumb = Array<{ label: string; href?: string }>\n\ntype SidebarCustomizationDraft = {\n order: string[]\n groupLabels: Record<string, string>\n itemLabels: Record<string, string>\n hiddenItemIds: Record<string, boolean>\n}\n\ntype SidebarGroup = AppShellProps['groups'][number]\ntype SidebarItem = SidebarGroup['items'][number]\ntype SidebarRoleTarget = { id: string; name: string; hasPreference: boolean }\n\nfunction resolveGroupKey(group: SidebarGroup): string {\n if (group.id && group.id.length) return group.id\n if (group.defaultName && group.defaultName.length) return slugifySidebarId(group.defaultName)\n return slugifySidebarId(group.name)\n}\n\nconst HeaderContext = React.createContext<{\n setBreadcrumb: (b?: Breadcrumb) => void\n setTitle: (t?: string) => void\n} | null>(null)\n\nexport function ApplyBreadcrumb({ breadcrumb, title, titleKey }: { breadcrumb?: Array<{ label: string; href?: string; labelKey?: string }>; title?: string; titleKey?: string }) {\n const ctx = React.useContext(HeaderContext)\n const t = useT()\n const resolvedBreadcrumb = React.useMemo<Breadcrumb | undefined>(() => {\n if (!breadcrumb) return undefined\n return breadcrumb.map(({ label, labelKey, href }) => {\n const translated = labelKey ? t(labelKey) : undefined\n const finalLabel = translated && translated !== labelKey ? translated : label\n return {\n href,\n label: finalLabel,\n }\n })\n }, [breadcrumb, t])\n const resolvedTitle = React.useMemo(() => {\n if (!titleKey) return title\n const translated = t(titleKey)\n if (translated && translated !== titleKey) return translated\n return title\n }, [titleKey, title, t])\n React.useEffect(() => {\n ctx?.setBreadcrumb(resolvedBreadcrumb)\n if (resolvedTitle !== undefined) ctx?.setTitle(resolvedTitle)\n }, [ctx, resolvedBreadcrumb, resolvedTitle])\n return null\n}\n\nconst DefaultIcon = (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\">\n <path d=\"M8 6h13M8 12h13M8 18h13\"/>\n <path d=\"M3 6h.01M3 12h.01M3 18h.01\"/>\n </svg>\n)\n\n// DataTable icon used for dynamic custom entity records links\nconst DataTableIcon = (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\">\n <rect x=\"3\" y=\"4\" width=\"18\" height=\"16\" rx=\"2\" ry=\"2\"/>\n <line x1=\"3\" y1=\"8\" x2=\"21\" y2=\"8\"/>\n <line x1=\"9\" y1=\"8\" x2=\"9\" y2=\"20\"/>\n <line x1=\"15\" y1=\"8\" x2=\"15\" y2=\"20\"/>\n </svg>\n)\n\nconst CustomizeIcon = (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\">\n <circle cx=\"12\" cy=\"12\" r=\"3\" />\n <path d=\"M19.4 15a1.65 1.65 0 0 0 .33 1.82l.05.05a2 2 0 1 1-2.83 2.83l-.05-.05A1.65 1.65 0 0 0 15 19.4a1.65 1.65 0 0 0-1 .6 1.65 1.65 0 0 0-.33 1.82l-.05.05a2 2 0 1 1-2.83-2.83l.05-.05A1.65 1.65 0 0 0 9 15a1.65 1.65 0 0 0-1-.6 1.65 1.65 0 0 0-1.82.33l-.05.05a2 2 0 1 1-2.83-2.83l.05-.05A1.65 1.65 0 0 0 4.6 9 1.65 1.65 0 0 0 4 8a1.65 1.65 0 0 0-.6-1.82l-.05-.05a2 2 0 1 1 2.83-2.83l.05.05A1.65 1.65 0 0 0 9 4.6a1.65 1.65 0 0 0 1-.6 1.65 1.65 0 0 0 .33-1.82l.05-.05a2 2 0 1 1 2.83 2.83l-.05.05A1.65 1.65 0 0 0 15 9a1.65 1.65 0 0 0 1 .6 1.65 1.65 0 0 0 1.82-.33l.05-.05a2 2 0 1 1 2.83 2.83l-.05.05A1.65 1.65 0 0 0 19.4 15z\" />\n </svg>\n)\n\nconst BackArrowIcon = (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <path d=\"M19 12H5M12 19l-7-7 7-7\" />\n </svg>\n)\n\nfunction Chevron({ open }: { open: boolean }) {\n return (\n <svg className={`transition-transform ${open ? 'rotate-180' : ''}`} width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\"><path d=\"M6 9l6 6 6-6\"/></svg>\n )\n}\n\nexport function AppShell({ productName, email, groups, rightHeaderSlot, children, sidebarCollapsedDefault = false, currentTitle, breadcrumb, adminNavApi, version, settingsSectionTitle, settingsPathPrefixes = [], settingsSections, profileSections, profileSectionTitle, profilePathPrefixes = [], mobileSidebarSlot }: AppShellProps) {\n const pathname = usePathname()\n const searchParams = useSearchParams()\n const t = useT()\n const locale = useLocale()\n const resolvedProductName = productName ?? t('appShell.productName')\n const [mobileOpen, setMobileOpen] = React.useState(false)\n // Initialize from server-provided prop only to avoid hydration flicker\n const [collapsed, setCollapsed] = React.useState(sidebarCollapsedDefault)\n // Maintain internal nav state so we can augment it client-side\n const [navGroups, setNavGroups] = React.useState(AppShell.cloneGroups(groups))\n const [openGroups, setOpenGroups] = React.useState<Record<string, boolean>>(() =>\n Object.fromEntries(groups.map((g) => [resolveGroupKey(g), true])) as Record<string, boolean>\n )\n const [customizing, setCustomizing] = React.useState(false)\n const [customDraft, setCustomDraft] = React.useState<SidebarCustomizationDraft | null>(null)\n const [loadingPreferences, setLoadingPreferences] = React.useState(false)\n const [savingPreferences, setSavingPreferences] = React.useState(false)\n const [customizationError, setCustomizationError] = React.useState<string | null>(null)\n const [availableRoleTargets, setAvailableRoleTargets] = React.useState<SidebarRoleTarget[]>([])\n const [selectedRoleIds, setSelectedRoleIds] = React.useState<string[]>([])\n const [canApplyToRoles, setCanApplyToRoles] = React.useState(false)\n const originalNavRef = React.useRef<SidebarGroup[] | null>(null)\n const [headerTitle, setHeaderTitle] = React.useState<string | undefined>(currentTitle)\n const [headerBreadcrumb, setHeaderBreadcrumb] = React.useState<Breadcrumb | undefined>(breadcrumb)\n const effectiveCollapsed = customizing ? false : collapsed\n const expandedSidebarWidth = customizing ? '320px' : '240px'\n const injectionContext = React.useMemo(\n () => ({\n path: pathname ?? '',\n query: searchParams?.toString() ?? '',\n }),\n [pathname, searchParams],\n )\n\n const isOnSettingsPath = React.useMemo(() => {\n if (!pathname) return false\n if (pathname === '/backend/settings') return true\n return settingsPathPrefixes.some((prefix) => pathname.startsWith(prefix))\n }, [pathname, settingsPathPrefixes])\n\n const isOnProfilePath = React.useMemo(() => {\n if (!pathname) return false\n if (pathname === '/backend/profile') return true\n return profilePathPrefixes.some((prefix) => pathname.startsWith(prefix))\n }, [pathname, profilePathPrefixes])\n\n const sidebarMode: 'main' | 'settings' | 'profile' =\n isOnSettingsPath ? 'settings' :\n isOnProfilePath ? 'profile' :\n 'main'\n\n // Lock body scroll when mobile drawer is open so touch scroll stays in the drawer\n React.useEffect(() => {\n if (!mobileOpen || typeof document === 'undefined') return\n const prev = document.body.style.overflow\n document.body.style.overflow = 'hidden'\n return () => {\n document.body.style.overflow = prev\n }\n }, [mobileOpen])\n\n React.useEffect(() => {\n try {\n const savedOpen = typeof window !== 'undefined' ? localStorage.getItem('om:sidebarOpenGroups') : null\n if (!savedOpen) return\n const parsed = JSON.parse(savedOpen) as Record<string, boolean>\n setOpenGroups((prev) => {\n const next = { ...prev }\n for (const group of groups) {\n const key = resolveGroupKey(group)\n if (key in parsed) next[key] = !!parsed[key]\n else if (group.name in parsed) next[key] = !!parsed[group.name]\n }\n return next\n })\n } catch {\n // ignore localStorage errors to avoid breaking hydration\n }\n }, [groups])\n\n const toggleGroup = (groupId: string) => setOpenGroups((prev) => ({ ...prev, [groupId]: prev[groupId] === false }))\n\n const updateDraft = React.useCallback((updater: (draft: SidebarCustomizationDraft) => SidebarCustomizationDraft) => {\n setCustomDraft((prev) => {\n if (!prev) return prev\n const next = updater(prev)\n if (originalNavRef.current) {\n setNavGroups(applyCustomizationDraft(originalNavRef.current, next))\n }\n return next\n })\n }, [])\n\n const startCustomization = React.useCallback(async () => {\n if (customizing || loadingPreferences) return\n setCustomizationError(null)\n setLoadingPreferences(true)\n try {\n const baseSnapshot = filterMainSidebarGroups(AppShell.cloneGroups(navGroups))\n const call = await apiCall<{\n settings?: Record<string, unknown>\n canApplyToRoles?: boolean\n roles?: Array<{ id?: string; name?: string; hasPreference?: boolean }>\n }>('/api/auth/sidebar/preferences')\n const data = call.ok ? (call.result ?? null) : null\n const rawSettings = data?.settings\n const responseOrder = Array.isArray(rawSettings?.groupOrder)\n ? rawSettings.groupOrder\n .map((id: unknown) => (typeof id === 'string' ? id.trim() : ''))\n .filter((id: string) => id.length > 0)\n : []\n const responseGroupLabels: Record<string, string> = {}\n if (rawSettings?.groupLabels && typeof rawSettings.groupLabels === 'object') {\n for (const [key, value] of Object.entries(rawSettings.groupLabels as Record<string, unknown>)) {\n if (typeof value !== 'string') continue\n const trimmedKey = key.trim()\n if (!trimmedKey) continue\n responseGroupLabels[trimmedKey] = value\n }\n }\n const responseItemLabels: Record<string, string> = {}\n if (rawSettings?.itemLabels && typeof rawSettings.itemLabels === 'object') {\n for (const [key, value] of Object.entries(rawSettings.itemLabels as Record<string, unknown>)) {\n if (typeof value !== 'string') continue\n const trimmedKey = key.trim()\n if (!trimmedKey) continue\n responseItemLabels[trimmedKey] = value\n }\n }\n const responseHiddenItems = Array.isArray(rawSettings?.hiddenItems)\n ? rawSettings.hiddenItems\n .map((href: unknown) => (typeof href === 'string' ? href.trim() : ''))\n .filter((href: string) => href.length > 0)\n : []\n const canManageRoles = data?.canApplyToRoles === true\n setCanApplyToRoles(canManageRoles)\n if (canManageRoles) {\n const roles = Array.isArray(data?.roles)\n ? (data.roles as Array<{ id?: string; name?: string; hasPreference?: boolean }>).filter((role) => typeof role?.id === 'string' && typeof role?.name === 'string')\n : []\n const mappedRoles: SidebarRoleTarget[] = roles.map((role) => ({\n id: role.id as string,\n name: role.name as string,\n hasPreference: role.hasPreference === true,\n }))\n setAvailableRoleTargets(mappedRoles)\n setSelectedRoleIds(mappedRoles.filter((role) => role.hasPreference).map((role) => role.id))\n } else {\n setAvailableRoleTargets([])\n setSelectedRoleIds([])\n }\n const currentIds = baseSnapshot.map((group) => resolveGroupKey(group))\n const order = mergeGroupOrder(responseOrder, currentIds)\n const { itemDefaults } = collectSidebarDefaults(baseSnapshot)\n const hiddenItemIds: Record<string, boolean> = {}\n for (const href of responseHiddenItems) {\n if (!itemDefaults.has(href)) continue\n hiddenItemIds[href] = true\n }\n const draft: SidebarCustomizationDraft = {\n order,\n groupLabels: { ...responseGroupLabels },\n itemLabels: { ...responseItemLabels },\n hiddenItemIds,\n }\n originalNavRef.current = baseSnapshot\n setCustomDraft(draft)\n setNavGroups(applyCustomizationDraft(baseSnapshot, draft))\n setCustomizing(true)\n } catch (error) {\n console.error('Failed to load sidebar preferences', error)\n setCustomizationError(t('appShell.sidebarCustomizationLoadError'))\n } finally {\n setLoadingPreferences(false)\n }\n }, [customizing, loadingPreferences, navGroups, t])\n\n const cancelCustomization = React.useCallback(() => {\n setCustomizing(false)\n setCustomDraft(null)\n setCustomizationError(null)\n setAvailableRoleTargets([])\n setSelectedRoleIds([])\n setCanApplyToRoles(false)\n if (originalNavRef.current) {\n setNavGroups(AppShell.cloneGroups(originalNavRef.current))\n }\n originalNavRef.current = null\n }, [])\n\n const resetCustomization = React.useCallback(() => {\n if (!originalNavRef.current) return\n const base = AppShell.cloneGroups(originalNavRef.current)\n const order = base.map((group) => resolveGroupKey(group))\n const draft: SidebarCustomizationDraft = { order, groupLabels: {}, itemLabels: {}, hiddenItemIds: {} }\n originalNavRef.current = base\n setCustomDraft(draft)\n setNavGroups(applyCustomizationDraft(base, draft))\n if (canApplyToRoles) {\n setSelectedRoleIds(availableRoleTargets.filter((role) => role.hasPreference).map((role) => role.id))\n }\n }, [availableRoleTargets, canApplyToRoles])\n\n const saveCustomization = React.useCallback(async () => {\n if (!customDraft) return\n setSavingPreferences(true)\n setCustomizationError(null)\n try {\n const baseGroups = originalNavRef.current ?? filterMainSidebarGroups(AppShell.cloneGroups(navGroups))\n const { groupDefaults, itemDefaults } = collectSidebarDefaults(baseGroups)\n const sanitizedGroupLabels: Record<string, string> = {}\n for (const [key, value] of Object.entries(customDraft.groupLabels)) {\n const trimmed = value.trim()\n const base = groupDefaults.get(key)\n if (!trimmed || !base) continue\n if (trimmed !== base) sanitizedGroupLabels[key] = trimmed\n }\n const sanitizedItemLabels: Record<string, string> = {}\n for (const [href, value] of Object.entries(customDraft.itemLabels)) {\n const trimmed = value.trim()\n const base = itemDefaults.get(href)\n if (!trimmed || !base) continue\n if (trimmed !== base) sanitizedItemLabels[href] = trimmed\n }\n const sanitizedHiddenItems: string[] = []\n for (const [href, hidden] of Object.entries(customDraft.hiddenItemIds)) {\n if (!hidden) continue\n if (!itemDefaults.has(href)) continue\n sanitizedHiddenItems.push(href)\n }\n const applyToRolesPayload = canApplyToRoles ? [...selectedRoleIds] : []\n const clearRoleIdsPayload = canApplyToRoles\n ? availableRoleTargets\n .filter((role) => role.hasPreference && !selectedRoleIds.includes(role.id))\n .map((role) => role.id)\n : []\n const payload: Record<string, unknown> = {\n groupOrder: customDraft.order,\n groupLabels: sanitizedGroupLabels,\n itemLabels: sanitizedItemLabels,\n hiddenItems: sanitizedHiddenItems,\n }\n if (canApplyToRoles) {\n payload.applyToRoles = applyToRolesPayload\n payload.clearRoleIds = clearRoleIdsPayload\n }\n const call = await apiCall<{\n canApplyToRoles?: boolean\n roles?: Array<{ id?: string; name?: string; hasPreference?: boolean }>\n }>('/api/auth/sidebar/preferences', {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n })\n if (!call.ok) {\n setCustomizationError(t('appShell.sidebarCustomizationSaveError'))\n return\n }\n const data = call.result ?? null\n if (data?.canApplyToRoles !== undefined) {\n setCanApplyToRoles(data.canApplyToRoles === true)\n }\n if (Array.isArray(data?.roles)) {\n const mappedRoles: SidebarRoleTarget[] = (data.roles as Array<{ id?: string; name?: string; hasPreference?: boolean }>).filter((role) => typeof role?.id === 'string' && typeof role?.name === 'string').map((role) => ({\n id: role.id as string,\n name: role.name as string,\n hasPreference: role.hasPreference === true,\n }))\n setAvailableRoleTargets(mappedRoles)\n setSelectedRoleIds(mappedRoles.filter((role) => role.hasPreference).map((role) => role.id))\n }\n originalNavRef.current = applyCustomizationDraft(baseGroups, customDraft)\n setNavGroups(AppShell.cloneGroups(originalNavRef.current))\n setCustomizing(false)\n setCustomDraft(null)\n try { window.dispatchEvent(new Event('om:refresh-sidebar')) } catch {}\n } catch (error) {\n console.error('Failed to save sidebar preferences', error)\n setCustomizationError(t('appShell.sidebarCustomizationSaveError'))\n } finally {\n setSavingPreferences(false)\n }\n }, [customDraft, navGroups, t])\n\n const moveGroup = React.useCallback((groupId: string, offset: number) => {\n updateDraft((draft) => {\n const order = [...draft.order]\n const index = order.indexOf(groupId)\n if (index === -1) return draft\n const nextIndex = Math.max(0, Math.min(order.length - 1, index + offset))\n if (nextIndex === index) return draft\n order.splice(index, 1)\n order.splice(nextIndex, 0, groupId)\n return { ...draft, order }\n })\n }, [updateDraft])\n\n const setGroupLabel = React.useCallback((groupId: string, value: string) => {\n updateDraft((draft) => {\n const next = { ...draft.groupLabels }\n if (value.trim().length === 0) delete next[groupId]\n else next[groupId] = value\n return { ...draft, groupLabels: next }\n })\n }, [updateDraft])\n\n const setItemLabel = React.useCallback((href: string, value: string) => {\n updateDraft((draft) => {\n const next = { ...draft.itemLabels }\n if (value.trim().length === 0) delete next[href]\n else next[href] = value\n return { ...draft, itemLabels: next }\n })\n }, [updateDraft])\n const setItemHidden = React.useCallback((href: string, hidden: boolean) => {\n updateDraft((draft) => {\n const next = { ...draft.hiddenItemIds }\n if (hidden) next[href] = true\n else delete next[href]\n return { ...draft, hiddenItemIds: next }\n })\n }, [updateDraft])\n\n const toggleRoleSelection = React.useCallback((roleId: string) => {\n setSelectedRoleIds((prev) => (prev.includes(roleId) ? prev.filter((id) => id !== roleId) : [...prev, roleId]))\n }, [])\n\n const asideWidth = effectiveCollapsed ? '72px' : expandedSidebarWidth\n // Use min-h-svh so the border extends with tall content; keep overflow for long menus\n const asideClassesBase = `border-r bg-background/60 py-4 min-h-svh overflow-y-auto`;\n\n // Persist collapse state to localStorage and cookie\n React.useEffect(() => {\n try { localStorage.setItem('om:sidebarCollapsed', collapsed ? '1' : '0') } catch {}\n try {\n document.cookie = `om_sidebar_collapsed=${collapsed ? '1' : '0'}; path=/; max-age=31536000; samesite=lax`\n } catch {}\n }, [collapsed])\n React.useEffect(() => {\n try { localStorage.setItem('om:sidebarOpenGroups', JSON.stringify(openGroups)) } catch {}\n }, [openGroups])\n\n // Ensure current route's group is expanded on load\n React.useEffect(() => {\n const activeGroup = navGroups.find((g) => g.items.some((i) => pathname?.startsWith(i.href)))\n if (!activeGroup) return\n const key = resolveGroupKey(activeGroup)\n setOpenGroups((prev) => (prev[key] === false ? { ...prev, [key]: true } : prev))\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [pathname, navGroups])\n // Keep header state in sync with props (server-side updates)\n React.useEffect(() => {\n setHeaderTitle(currentTitle)\n setHeaderBreadcrumb(breadcrumb)\n }, [currentTitle, breadcrumb])\n\n // Keep navGroups in sync when server-provided groups change\n React.useEffect(() => {\n if (customizing && customDraft && originalNavRef.current) {\n originalNavRef.current = filterMainSidebarGroups(AppShell.cloneGroups(groups))\n setNavGroups(applyCustomizationDraft(originalNavRef.current, customDraft))\n return\n }\n setNavGroups(AppShell.cloneGroups(groups))\n }, [groups, customizing, customDraft])\n\n // Optional: full refresh from adminNavApi, used to reflect RBAC/org/entity changes without page reload\n React.useEffect(() => {\n let cancelled = false\n function indexIcons(groupsToIndex: AppShellProps['groups']): Map<string, React.ReactNode | undefined> {\n const map = new Map<string, React.ReactNode | undefined>()\n for (const g of groupsToIndex) {\n for (const i of g.items) {\n map.set(i.href, i.icon)\n if (i.children) for (const c of i.children) map.set(c.href, c.icon)\n }\n }\n return map\n }\n function mergePreservingIcons(oldG: AppShellProps['groups'], newG: AppShellProps['groups']): AppShellProps['groups'] {\n const iconMap = indexIcons(oldG)\n const merged = newG.map((g) => ({\n id: g.id,\n name: g.name,\n defaultName: g.defaultName,\n items: g.items.map((i) => ({\n href: i.href,\n title: i.title,\n defaultTitle: i.defaultTitle,\n enabled: i.enabled,\n hidden: i.hidden,\n icon: i.icon ?? iconMap.get(i.href),\n pageContext: i.pageContext,\n children: i.children?.map((c) => ({\n href: c.href,\n title: c.title,\n defaultTitle: c.defaultTitle,\n enabled: c.enabled,\n hidden: c.hidden,\n icon: c.icon ?? iconMap.get(c.href),\n pageContext: c.pageContext,\n })),\n })),\n }))\n return merged\n }\n async function refreshFullNav() {\n if (!adminNavApi) return\n try {\n const call = await apiCall<{ groups?: unknown[] }>(adminNavApi, { credentials: 'include' as any })\n if (!call.ok) return\n const data = call.result\n if (cancelled) return\n const nextGroups = Array.isArray(data?.groups) ? data.groups : []\n if (nextGroups.length) setNavGroups((prev) => AppShell.cloneGroups(mergePreservingIcons(prev, nextGroups as any)))\n } catch {}\n }\n // Refresh on window focus\n const onFocus = () => refreshFullNav()\n window.addEventListener('focus', onFocus)\n return () => { cancelled = true; window.removeEventListener('focus', onFocus) }\n }, [adminNavApi])\n\n // Refresh sidebar when other parts of the app dispatch an explicit event\n React.useEffect(() => {\n if (!adminNavApi) return\n const api = adminNavApi as string\n let cancelled = false\n function indexIcons(groupsToIndex: AppShellProps['groups']): Map<string, React.ReactNode | undefined> {\n const map = new Map<string, React.ReactNode | undefined>()\n for (const g of groupsToIndex) {\n for (const i of g.items) {\n map.set(i.href, i.icon)\n if (i.children) for (const c of i.children) map.set(c.href, c.icon)\n }\n }\n return map\n }\n function mergePreservingIcons(oldG: AppShellProps['groups'], newG: AppShellProps['groups']): AppShellProps['groups'] {\n const iconMap = indexIcons(oldG)\n const merged = newG.map((g) => ({\n id: g.id,\n name: g.name,\n defaultName: g.defaultName,\n items: g.items.map((i) => ({\n href: i.href,\n title: i.title,\n defaultTitle: i.defaultTitle,\n enabled: i.enabled,\n hidden: i.hidden,\n icon: i.icon ?? iconMap.get(i.href),\n pageContext: i.pageContext,\n children: i.children?.map((c) => ({\n href: c.href,\n title: c.title,\n defaultTitle: c.defaultTitle,\n enabled: c.enabled,\n hidden: c.hidden,\n icon: c.icon ?? iconMap.get(c.href),\n pageContext: c.pageContext,\n })),\n })),\n }))\n return merged\n }\n async function refreshFullNav() {\n try {\n const call = await apiCall<{ groups?: unknown[] }>(api, { credentials: 'include' as any })\n if (!call.ok) return\n const data = call.result\n if (cancelled) return\n const nextGroups = Array.isArray(data?.groups) ? data.groups : []\n if (nextGroups.length) setNavGroups((prev) => AppShell.cloneGroups(mergePreservingIcons(prev, nextGroups as any)))\n } catch {}\n }\n const onRefresh = () => { refreshFullNav() }\n window.addEventListener('om:refresh-sidebar', onRefresh as any)\n return () => { cancelled = true; window.removeEventListener('om:refresh-sidebar', onRefresh as any) }\n }, [adminNavApi])\n\n // adminNavApi already includes user entities; no extra fetch\n\n function renderSectionSidebar(\n sections: SectionNavGroup[],\n title: string,\n compact: boolean,\n hideHeader?: boolean\n ) {\n const sortedSections = [...sections].sort((a, b) => (a.order ?? 0) - (b.order ?? 0))\n const lastVisibleIndex = sortedSections.length - 1\n\n return (\n <div className=\"flex flex-col min-h-full gap-3\">\n {!hideHeader && (\n <div className={`flex items-center ${compact ? 'justify-center' : 'justify-between'} mb-2`}>\n <Link href=\"/backend\" className=\"flex items-center gap-2\" aria-label={t('appShell.goToDashboard')}>\n <Image src=\"/open-mercato.svg\" alt={resolvedProductName} width={32} height={32} className=\"rounded m-4\" />\n {!compact && <div className=\"text-m font-semibold\">{resolvedProductName}</div>}\n </Link>\n </div>\n )}\n <div className=\"flex flex-1 flex-col gap-3 overflow-y-auto pr-1\">\n <Link\n href=\"/backend\"\n className={`flex items-center gap-2 ${compact ? 'justify-center px-2' : 'px-2'} py-1 text-sm text-muted-foreground hover:text-foreground transition-colors`}\n aria-label={t('backend.nav.backToMain', 'Back')}\n >\n <span className=\"flex items-center justify-center shrink-0\">{BackArrowIcon}</span>\n {!compact && <span>{title}</span>}\n </Link>\n <nav className=\"flex flex-col gap-2\">\n {sortedSections.map((section, sectionIndex) => {\n const visibleItems = section.items\n if (visibleItems.length === 0) return null\n const sortedItems = [...visibleItems].sort((a, b) => (a.order ?? 0) - (b.order ?? 0))\n const sectionLabel = section.labelKey ? t(section.labelKey, section.label) : section.label\n const sectionKey = `settings:${section.id}`\n const open = openGroups[sectionKey] !== false\n\n return (\n <div key={section.id}>\n <Button\n variant=\"muted\"\n onClick={() => toggleGroup(sectionKey)}\n className={`w-full ${compact ? 'px-0 justify-center' : 'px-2 justify-between'} flex text-xs uppercase text-muted-foreground/90 py-2`}\n aria-expanded={open}\n >\n {!compact && <span>{sectionLabel}</span>}\n {!compact && <Chevron open={open} />}\n </Button>\n {open && (\n <div className={`flex flex-col ${compact ? 'items-center' : ''} gap-1 ${!compact ? 'pl-1' : ''}`}>\n {sortedItems.map((item) => {\n const isActive = pathname === item.href || pathname?.startsWith(item.href + '/')\n const label = item.labelKey ? t(item.labelKey, item.label) : item.label\n const base = compact ? 'w-10 h-10 justify-center' : 'px-2 py-1 gap-2'\n\n return (\n <Link\n key={item.id}\n href={item.href}\n className={`relative text-sm rounded inline-flex items-center ${base} ${\n isActive\n ? 'bg-background border shadow-sm'\n : 'hover:bg-accent hover:text-accent-foreground'\n }`}\n title={compact ? label : undefined}\n onClick={() => setMobileOpen(false)}\n >\n {isActive && (\n <span className=\"absolute left-0 top-1 bottom-1 w-0.5 rounded bg-foreground\" />\n )}\n <span className={`flex items-center justify-center shrink-0 ${compact ? '' : 'text-muted-foreground'}`}>\n {item.icon ?? DefaultIcon}\n </span>\n {!compact && <span className=\"truncate\">{label}</span>}\n </Link>\n )\n })}\n </div>\n )}\n {sectionIndex !== lastVisibleIndex && <div className=\"my-2 border-t border-dotted\" />}\n </div>\n )\n })}\n </nav>\n </div>\n </div>\n )\n }\n\n function renderSidebar(compact: boolean, hideHeader?: boolean) {\n if (sidebarMode === 'settings' && settingsSections && settingsSections.length > 0) {\n return renderSectionSidebar(\n settingsSections,\n settingsSectionTitle ?? t('backend.nav.settings', 'Settings'),\n compact,\n hideHeader\n )\n }\n\n if (sidebarMode === 'profile' && profileSections && profileSections.length > 0) {\n return renderSectionSidebar(\n profileSections,\n profileSectionTitle ?? t('backend.nav.profile', 'Profile'),\n compact,\n hideHeader\n )\n }\n\n const isMobileVariant = !!hideHeader\n const shouldRenderSidebarInjectionSpots = !isMobileVariant\n const baseGroupsForDefaults = originalNavRef.current ?? navGroups\n const baseGroupMap = new Map<string, SidebarGroup>()\n for (const group of baseGroupsForDefaults) {\n baseGroupMap.set(resolveGroupKey(group), group)\n }\n const localeLabel = (locale || '').toUpperCase()\n\n const orderedGroupIds = customDraft\n ? mergeGroupOrder(customDraft.order, Array.from(baseGroupMap.keys()))\n : navGroups.map((group) => resolveGroupKey(group))\n\n const lastVisibleGroupIndex = (() => {\n for (let idx = navGroups.length - 1; idx >= 0; idx -= 1) {\n if (navGroups[idx].items.some((item) => item.hidden !== true)) return idx\n }\n return -1\n })()\n\n const renderEditableItems = (baseItems: SidebarItem[], currentItems: SidebarItem[], depth = 0): React.ReactNode => {\n if (!customDraft) return null\n return baseItems.map((baseItem) => {\n const current = currentItems.find((item) => item.href === baseItem.href) ?? baseItem\n const placeholder = baseItem.defaultTitle ?? baseItem.title\n const value = customDraft.itemLabels[baseItem.href] ?? ''\n const hidden = customDraft.hiddenItemIds[baseItem.href] === true\n return (\n <div\n key={baseItem.href}\n className={`flex flex-col gap-1 ${hidden ? 'opacity-60' : ''}`}\n style={depth ? { marginLeft: depth * 16 } : undefined}\n >\n <span className=\"text-xs font-medium text-muted-foreground\">{placeholder}</span>\n <div className=\"flex items-center gap-2\">\n <input\n type=\"checkbox\"\n className=\"h-4 w-4 accent-foreground\"\n checked={!hidden}\n onChange={(event) => setItemHidden(baseItem.href, !event.target.checked)}\n disabled={savingPreferences}\n aria-label={t('appShell.sidebarCustomizationShowItem')}\n title={t('appShell.sidebarCustomizationShowItem')}\n />\n <input\n value={value}\n onChange={(event) => setItemLabel(baseItem.href, event.target.value)}\n placeholder={placeholder}\n disabled={savingPreferences}\n className=\"h-8 flex-1 rounded border bg-background px-2 text-sm shadow-sm focus:outline-none focus:ring-2 focus:ring-ring disabled:opacity-60\"\n />\n </div>\n {baseItem.children && baseItem.children.length > 0 ? (\n <div className=\"flex flex-col gap-1\">\n {renderEditableItems(baseItem.children, current.children ?? [], depth + 1)}\n </div>\n ) : null}\n </div>\n )\n })\n }\n\n const customizationEditor = customizing ? (\n customDraft ? (\n <div className=\"flex flex-col gap-3 rounded border border-dashed bg-muted/20 p-3\">\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <div className=\"text-sm font-semibold\">{t('appShell.sidebarCustomizationHeading')}</div>\n <div className=\"flex items-center gap-2\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={resetCustomization}\n disabled={savingPreferences}\n >\n {t('appShell.sidebarCustomizationReset')}\n </Button>\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={cancelCustomization}\n disabled={savingPreferences}\n >\n {t('appShell.sidebarCustomizationCancel')}\n </Button>\n <Button\n size=\"sm\"\n className=\"bg-foreground text-background hover:bg-foreground/90\"\n onClick={saveCustomization}\n disabled={savingPreferences}\n >\n {savingPreferences ? t('appShell.sidebarCustomizationSaving') : t('appShell.sidebarCustomizationSave')}\n </Button>\n </div>\n </div>\n <p className=\"text-xs text-muted-foreground\">{t('appShell.sidebarCustomizationHint', { locale: localeLabel })}</p>\n {canApplyToRoles ? (\n <div className=\"flex flex-col gap-2 rounded border bg-background/70 p-3 shadow-sm\">\n <div>\n <div className=\"text-sm font-semibold\">{t('appShell.sidebarApplyToRolesTitle')}</div>\n <p className=\"text-xs text-muted-foreground\">{t('appShell.sidebarApplyToRolesDescription')}</p>\n </div>\n {availableRoleTargets.length > 0 ? (\n <div className=\"flex flex-col gap-2\">\n {availableRoleTargets.map((role) => {\n const checked = selectedRoleIds.includes(role.id)\n const willClear = role.hasPreference && !checked\n return (\n <label key={role.id} className=\"flex items-center gap-2 rounded border bg-background px-2 py-1 text-sm shadow-sm\">\n <input\n type=\"checkbox\"\n className=\"h-4 w-4 accent-foreground\"\n checked={checked}\n onChange={() => toggleRoleSelection(role.id)}\n disabled={savingPreferences}\n />\n <span className=\"flex-1 truncate\">{role.name}</span>\n {role.hasPreference ? (\n <span className={`text-xs ${willClear ? 'text-destructive' : 'text-muted-foreground'}`}>\n {willClear ? t('appShell.sidebarRoleWillClear') : t('appShell.sidebarRoleHasPreset')}\n </span>\n ) : null}\n </label>\n )\n })}\n </div>\n ) : (\n <p className=\"text-xs text-muted-foreground\">{t('appShell.sidebarApplyToRolesEmpty')}</p>\n )}\n </div>\n ) : null}\n {customizationError ? <p className=\"text-xs text-destructive\">{customizationError}</p> : null}\n <div className=\"flex flex-col gap-3\">\n {orderedGroupIds.map((groupId, index) => {\n const baseGroup = baseGroupMap.get(groupId)\n if (!baseGroup) return null\n const currentGroup = navGroups.find((group) => resolveGroupKey(group) === groupId) ?? baseGroup\n const placeholder = baseGroup.defaultName ?? baseGroup.name\n const value = customDraft.groupLabels[groupId] ?? ''\n return (\n <div key={groupId} className=\"flex flex-col gap-3 rounded border bg-background p-3 shadow-sm\">\n <div className={`flex ${compact ? 'flex-col gap-2' : 'items-center gap-2'}`}>\n <div className=\"flex-1\">\n <span className=\"text-xs font-medium text-muted-foreground\">{t('appShell.sidebarCustomizationGroupLabel')}</span>\n <input\n value={value}\n onChange={(event) => setGroupLabel(groupId, event.target.value)}\n placeholder={placeholder}\n disabled={savingPreferences}\n className=\"mt-1 h-8 w-full rounded border bg-background px-2 text-sm shadow-sm focus:outline-none focus:ring-2 focus:ring-ring disabled:opacity-60\"\n />\n </div>\n <div className=\"flex items-center gap-1 self-start\">\n <IconButton\n variant=\"outline\"\n size=\"sm\"\n className=\"text-muted-foreground hover:text-foreground\"\n onClick={() => moveGroup(groupId, -1)}\n disabled={index === 0 || savingPreferences}\n aria-label={t('appShell.sidebarCustomizationMoveUp')}\n >\n <ChevronUp className=\"size-4\" />\n </IconButton>\n <IconButton\n variant=\"outline\"\n size=\"sm\"\n className=\"text-muted-foreground hover:text-foreground\"\n onClick={() => moveGroup(groupId, 1)}\n disabled={index === orderedGroupIds.length - 1 || savingPreferences}\n aria-label={t('appShell.sidebarCustomizationMoveDown')}\n >\n <ChevronDown className=\"size-4\" />\n </IconButton>\n </div>\n </div>\n <div className=\"flex flex-col gap-2\">\n {renderEditableItems(baseGroup.items, currentGroup.items)}\n </div>\n </div>\n )\n })}\n </div>\n </div>\n ) : (\n <div className=\"rounded border border-dashed bg-muted/20 p-3 text-sm text-muted-foreground\">\n {t('appShell.sidebarCustomizationLoading')}\n </div>\n )\n ) : null\n\n return (\n <div className=\"flex flex-col min-h-full gap-3\">\n {!hideHeader && (\n <div className={`flex items-center ${compact ? 'justify-center' : 'justify-between'} mb-2`}>\n <Link href=\"/backend\" className=\"flex items-center gap-2\" aria-label={t('appShell.goToDashboard')}>\n <Image src=\"/open-mercato.svg\" alt={resolvedProductName} width={32} height={32} className=\"rounded m-4\" />\n {!compact && <div className=\"text-m font-semibold\">{resolvedProductName}</div>}\n </Link>\n </div>\n )}\n {shouldRenderSidebarInjectionSpots ? (\n <InjectionSpot\n spotId={BACKEND_SIDEBAR_TOP_INJECTION_SPOT_ID}\n context={injectionContext}\n />\n ) : null}\n <div className=\"flex flex-1 flex-col gap-3 overflow-y-auto pr-1\">\n {customizing ? (\n customizationEditor\n ) : (\n (() => {\n const isSettingsPath = (href: string) => {\n if (href === '/backend/settings') return true\n return settingsPathPrefixes.some((prefix) => href.startsWith(prefix))\n }\n\n const isMainItem = (item: SidebarItem) => {\n if (item.pageContext && item.pageContext !== 'main') return false\n if (isSettingsPath(item.href)) return false\n return true\n }\n\n const mainGroups = navGroups.map((g) => ({\n ...g,\n items: g.items.filter((item) => isMainItem(item) && item.hidden !== true),\n })).filter((g) => g.items.length > 0)\n\n const mainLastVisibleGroupIndex = (() => {\n for (let idx = mainGroups.length - 1; idx >= 0; idx -= 1) {\n if (mainGroups[idx].items.some((item) => item.hidden !== true)) return idx\n }\n return -1\n })()\n\n return (\n <>\n <nav className=\"flex flex-col gap-2\">\n {mainGroups.map((g, gi) => {\n const groupId = resolveGroupKey(g)\n const open = openGroups[groupId] !== false\n const visibleItems = g.items.filter((item) => item.hidden !== true)\n if (visibleItems.length === 0) return null\n return (\n <div key={groupId}>\n <Button\n variant=\"muted\"\n onClick={() => toggleGroup(groupId)}\n className={`w-full ${compact ? 'px-0 justify-center' : 'px-2 justify-between'} flex text-xs uppercase text-muted-foreground/90 py-2`}\n aria-expanded={open}\n >\n {!compact && <span>{g.name}</span>}\n {!compact && <Chevron open={open} />}\n </Button>\n {open && (\n <div className={`flex flex-col ${compact ? 'items-center' : ''} gap-1 ${!compact ? 'pl-1' : ''}`}>\n {visibleItems.map((i) => {\n const childItems = (i.children ?? []).filter((child) => child.hidden !== true)\n const showChildren = !!pathname && childItems.length > 0 && pathname.startsWith(i.href)\n const hasActiveChild = !!(pathname && childItems.some((c) => pathname.startsWith(c.href)))\n const isParentActive = (pathname === i.href) || (showChildren && !hasActiveChild)\n const base = compact ? 'w-10 h-10 justify-center' : 'px-2 py-1 gap-2'\n return (\n <React.Fragment key={i.href}>\n <Link\n href={i.href}\n className={`relative text-sm rounded inline-flex items-center ${base} ${\n isParentActive ? 'bg-background border shadow-sm' : 'hover:bg-accent hover:text-accent-foreground'\n } ${i.enabled === false ? 'pointer-events-none opacity-50' : ''}`}\n aria-disabled={i.enabled === false}\n title={compact ? i.title : undefined}\n onClick={() => setMobileOpen(false)}\n >\n {isParentActive ? (\n <span className=\"absolute left-0 top-1 bottom-1 w-0.5 rounded bg-foreground\" />\n ) : null}\n <span className={`flex items-center justify-center shrink-0 ${compact ? '' : 'text-muted-foreground'}`}>\n {i.icon ?? DefaultIcon}\n </span>\n {!compact && <span>{i.title}</span>}\n </Link>\n {showChildren ? (\n <div className={`flex flex-col ${compact ? 'items-center' : ''} gap-1 ${!compact ? 'pl-4' : ''}`}>\n {childItems.map((c) => {\n const childActive = pathname?.startsWith(c.href)\n const childBase = compact ? 'w-10 h-8 justify-center' : 'px-2 py-1 gap-2'\n return (\n <Link\n key={c.href}\n href={c.href}\n className={`relative text-sm rounded inline-flex items-center ${childBase} ${\n childActive ? 'bg-background border shadow-sm' : 'hover:bg-accent hover:text-accent-foreground'\n } ${c.enabled === false ? 'pointer-events-none opacity-50' : ''}`}\n aria-disabled={c.enabled === false}\n title={compact ? c.title : undefined}\n onClick={() => setMobileOpen(false)}\n >\n {childActive ? (\n <span className=\"absolute left-0 top-1 bottom-1 w-0.5 rounded bg-foreground\" />\n ) : null}\n <span className={`flex items-center justify-center shrink-0 ${compact ? '' : 'text-muted-foreground'}`}>\n {c.icon ?? (c.href.includes('/backend/entities/user/') && c.href.endsWith('/records') ? DataTableIcon : DefaultIcon)}\n </span>\n {!compact && <span>{c.title}</span>}\n </Link>\n )\n })}\n </div>\n ) : null}\n </React.Fragment>\n )\n })}\n </div>\n )}\n {gi !== mainLastVisibleGroupIndex && <div className=\"my-2 border-t border-dotted\" />}\n </div>\n )\n })}\n </nav>\n <div className=\"mt-4 pt-4 border-t\">\n <Link\n href=\"/backend/settings\"\n className={`relative text-sm rounded inline-flex items-center w-full ${\n compact ? 'w-10 h-10 justify-center' : 'px-2 py-1 gap-2'\n } ${\n pathname?.startsWith('/backend/settings') || pathname?.startsWith('/backend/config') || pathname?.startsWith('/backend/users') || pathname?.startsWith('/backend/roles') || pathname?.startsWith('/backend/api-keys') || pathname?.startsWith('/backend/entities') || pathname?.startsWith('/backend/query-indexes') || pathname?.startsWith('/backend/definitions') || pathname?.startsWith('/backend/instances') || pathname?.startsWith('/backend/tasks') || pathname?.startsWith('/backend/events') || pathname?.startsWith('/backend/rules') || pathname?.startsWith('/backend/sets') || pathname?.startsWith('/backend/logs') || pathname?.startsWith('/backend/directory') || pathname?.startsWith('/backend/feature-toggles')\n ? 'bg-background border shadow-sm font-medium'\n : 'hover:bg-accent hover:text-accent-foreground'\n }`}\n title={compact ? t('backend.nav.settings', 'Settings') : undefined}\n onClick={() => setMobileOpen(false)}\n >\n {(pathname?.startsWith('/backend/settings') || pathname?.startsWith('/backend/config') || pathname?.startsWith('/backend/users') || pathname?.startsWith('/backend/roles') || pathname?.startsWith('/backend/api-keys') || pathname?.startsWith('/backend/entities') || pathname?.startsWith('/backend/query-indexes') || pathname?.startsWith('/backend/definitions') || pathname?.startsWith('/backend/instances') || pathname?.startsWith('/backend/tasks') || pathname?.startsWith('/backend/events') || pathname?.startsWith('/backend/rules') || pathname?.startsWith('/backend/sets') || pathname?.startsWith('/backend/logs') || pathname?.startsWith('/backend/directory') || pathname?.startsWith('/backend/feature-toggles')) && (\n <span className=\"absolute left-0 top-1 bottom-1 w-0.5 rounded bg-foreground\" />\n )}\n <span className={`flex items-center justify-center shrink-0 ${compact ? '' : 'text-muted-foreground'}`}>\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"3\" />\n <path d=\"M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z\" />\n </svg>\n </span>\n {!compact && <span>{t('backend.nav.settings', 'Settings')}</span>}\n </Link>\n </div>\n </>\n )\n })()\n )}\n </div>\n {!customizing && (\n <>\n {compact || isMobileVariant ? (\n <IconButton\n variant=\"outline\"\n size=\"lg\"\n className=\"mt-auto\"\n onClick={startCustomization}\n disabled={loadingPreferences}\n aria-label={t('appShell.customizeSidebar')}\n >\n {CustomizeIcon}\n </IconButton>\n ) : (\n <Button\n variant=\"outline\"\n size=\"default\"\n className=\"mt-auto\"\n onClick={startCustomization}\n disabled={loadingPreferences}\n aria-label={t('appShell.customizeSidebar')}\n >\n {CustomizeIcon}\n {loadingPreferences ? t('appShell.sidebarCustomizationLoading') : t('appShell.customizeSidebar')}\n </Button>\n )}\n </>\n )}\n {shouldRenderSidebarInjectionSpots ? (\n <InjectionSpot\n spotId={BACKEND_SIDEBAR_FOOTER_INJECTION_SPOT_ID}\n context={injectionContext}\n />\n ) : null}\n </div>\n )\n }\n\n const gridColsClass = customizing\n ? 'lg:grid-cols-[320px_1fr]'\n : (effectiveCollapsed ? 'lg:grid-cols-[72px_1fr]' : 'lg:grid-cols-[240px_1fr]')\n const headerCtxValue = React.useMemo(() => ({\n setBreadcrumb: setHeaderBreadcrumb,\n setTitle: setHeaderTitle,\n }), [])\n\n return (\n <HeaderContext.Provider value={headerCtxValue}>\n <div className={`min-h-svh lg:grid ${gridColsClass}`}>\n {/* Desktop sidebar */}\n <aside className={`${asideClassesBase} ${effectiveCollapsed ? 'px-2' : 'px-3'} hidden lg:block`} style={{ width: asideWidth }}>{renderSidebar(effectiveCollapsed)}</aside>\n\n <div className=\"flex min-h-svh flex-col min-w-0\">\n <header className=\"border-b bg-background/60 px-3 lg:px-4 py-2 lg:py-3 flex items-center justify-between gap-2\">\n <div className=\"flex items-center gap-2 min-w-0\">\n {/* Mobile menu button */}\n <IconButton variant=\"outline\" size=\"sm\" className=\"lg:hidden\" aria-label={t('appShell.openMenu')} onClick={() => setMobileOpen(true)}>\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\"><path d=\"M3 6h18M3 12h18M3 18h18\"/></svg>\n </IconButton>\n {/* Desktop collapse toggle */}\n <IconButton\n variant=\"outline\"\n size=\"sm\"\n className=\"hidden lg:inline-flex\"\n aria-label={t('appShell.toggleSidebar')}\n onClick={() => setCollapsed((c) => !c)}\n disabled={customizing}\n >\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\">\n <rect x=\"3\" y=\"4\" width=\"18\" height=\"16\" rx=\"2\"/>\n <path d=\"M9 4v16\"/>\n </svg>\n </IconButton>\n {/* Header breadcrumb: always starts with Dashboard */}\n {(() => {\n const dashboardLabel = t('dashboard.title')\n const root: Breadcrumb = [{ label: dashboardLabel, href: '/backend' }]\n let rest: Breadcrumb = []\n if (headerBreadcrumb && headerBreadcrumb.length) {\n const first = headerBreadcrumb[0]\n const dup = first && (first.href === '/backend' || first.label === dashboardLabel || first.label?.toLowerCase() === 'dashboard')\n rest = dup ? headerBreadcrumb.slice(1) : headerBreadcrumb\n } else if (headerTitle) {\n rest = [{ label: headerTitle }]\n }\n const items = [...root, ...rest]\n const lastIndex = items.length - 1\n return (\n <nav className=\"flex items-center gap-2 text-sm min-w-0\">\n {items.map((b, i) => {\n const isLast = i === lastIndex\n const hiddenOnMobile = !isLast ? 'hidden md:inline' : ''\n return (\n <React.Fragment key={i}>\n {i > 0 && <span className={`text-muted-foreground hidden md:inline`}>/</span>}\n {b.href ? (\n <Link href={b.href} className={`text-muted-foreground hover:text-foreground ${hiddenOnMobile}`}>\n {b.label}\n </Link>\n ) : (\n <span className={`font-medium truncate max-w-[45vw] md:max-w-[60vw]`}>{b.label}</span>\n )}\n </React.Fragment>\n )\n })}\n </nav>\n )\n })()}\n </div>\n <div className=\"flex items-center gap-1 md:gap-2 text-sm shrink-0\">\n {rightHeaderSlot ? (\n rightHeaderSlot\n ) : (\n <span className=\"opacity-80\">{email || t('appShell.userFallback')}</span>\n )}\n </div>\n </header>\n <ProgressTopBar t={t} className=\"sticky top-0 z-10\" />\n <main className=\"flex-1 p-4 lg:p-6\">\n <InjectionSpot spotId={BACKEND_LAYOUT_TOP_INJECTION_SPOT_ID} context={injectionContext} />\n <FlashMessages />\n <PartialIndexBanner />\n <UpgradeActionBanner />\n <LastOperationBanner />\n <InjectionSpot spotId={BACKEND_RECORD_CURRENT_INJECTION_SPOT_ID} context={injectionContext} />\n <InjectionSpot\n spotId={LEGACY_GLOBAL_MUTATION_INJECTION_SPOT_ID}\n context={injectionContext}\n />\n <div id=\"om-top-banners\" className=\"mb-3 space-y-2\" />\n {children}\n <InjectionSpot spotId={BACKEND_LAYOUT_FOOTER_INJECTION_SPOT_ID} context={injectionContext} />\n </main>\n <footer className=\"border-t bg-background/80 backdrop-blur supports-[backdrop-filter]:bg-background/50 px-4 py-3 flex flex-wrap items-center justify-end gap-4\">\n {version ? (\n <span className=\"text-xs text-muted-foreground\">\n {t('appShell.version', { version })}\n </span>\n ) : null}\n <nav className=\"flex items-center gap-3 text-xs text-muted-foreground\">\n <Link href=\"/terms\" className=\"transition hover:text-foreground\">\n {t('common.terms')}\n </Link>\n <Link href=\"/privacy\" className=\"transition hover:text-foreground\">\n {t('common.privacy')}\n </Link>\n </nav>\n </footer>\n </div>\n\n {/* Mobile drawer */}\n {mobileOpen && (\n <div className=\"lg:hidden fixed inset-0 z-50\">\n <div className=\"absolute inset-0 bg-black/40\" onClick={() => setMobileOpen(false)} />\n <aside className=\"absolute left-0 top-0 flex h-full w-[260px] flex-col bg-background border-r overflow-hidden\">\n <div className=\"shrink-0 p-3 pb-2 flex items-center justify-between border-b\">\n <Link href=\"/backend\" className=\"flex items-center gap-2 text-sm font-semibold\" onClick={() => setMobileOpen(false)} aria-label={t('appShell.goToDashboard')}>\n <Image src=\"/open-mercato.svg\" alt={resolvedProductName} width={28} height={28} className=\"rounded\" />\n {resolvedProductName}\n </Link>\n <IconButton variant=\"outline\" size=\"sm\" onClick={() => setMobileOpen(false)} aria-label={t('appShell.closeMenu')}>\u2715</IconButton>\n </div>\n {mobileSidebarSlot && (\n <div className=\"shrink-0 border-b px-3 py-2\">\n {mobileSidebarSlot}\n </div>\n )}\n <div className=\"min-h-0 flex-1 overflow-y-auto overflow-x-hidden p-3\">\n {/* Force expanded sidebar in mobile drawer, hide its header and collapse toggle */}\n {renderSidebar(false, true)}\n </div>\n </aside>\n </div>\n )}\n </div>\n </HeaderContext.Provider>\n )\n}\n\n// Helper: deep-clone minimal shape we mutate (children arrays)\nAppShell.cloneGroups = function cloneGroups(groups: AppShellProps['groups']): AppShellProps['groups'] {\n const cloneItem = (item: SidebarItem): SidebarItem => ({\n href: item.href,\n title: item.title,\n defaultTitle: item.defaultTitle,\n icon: item.icon,\n enabled: item.enabled,\n hidden: item.hidden,\n pageContext: item.pageContext,\n children: item.children ? item.children.map((child) => cloneItem(child)) : undefined,\n })\n return groups.map((group) => ({\n id: group.id,\n name: group.name,\n defaultName: group.defaultName,\n items: group.items.map((item) => cloneItem(item)),\n }))\n}\n\nfunction applyCustomizationDraft(baseGroups: SidebarGroup[], draft: SidebarCustomizationDraft): SidebarGroup[] {\n const clones = AppShell.cloneGroups(baseGroups)\n const byId = new Map<string, SidebarGroup>()\n for (const group of clones) {\n byId.set(resolveGroupKey(group), group)\n }\n const orderedIds = mergeGroupOrder(draft.order, Array.from(byId.keys()))\n const seen = new Set<string>()\n const result: SidebarGroup[] = []\n for (const id of orderedIds) {\n if (seen.has(id)) continue\n const group = byId.get(id)\n if (!group) continue\n seen.add(id)\n const baseName = group.defaultName ?? group.name\n const override = draft.groupLabels[id]?.trim()\n result.push({\n ...group,\n name: override && override.length > 0 ? override : baseName,\n items: group.items.map((item) => applyItemDraft(item, draft)),\n })\n }\n return result\n}\n\nfunction applyItemDraft(item: SidebarItem, draft: SidebarCustomizationDraft): SidebarItem {\n const baseTitle = item.defaultTitle ?? item.title\n const override = draft.itemLabels[item.href]?.trim()\n const children = item.children\n ? item.children\n .map((child) => applyItemDraft(child, draft))\n : undefined\n const hidden = draft.hiddenItemIds[item.href] === true\n return {\n ...item,\n title: override && override.length > 0 ? override : baseTitle,\n hidden,\n children,\n }\n}\n\nfunction mergeGroupOrder(preferred: string[], current: string[]): string[] {\n const seen = new Set<string>()\n const merged: string[] = []\n for (const id of preferred) {\n const trimmed = id.trim()\n if (!trimmed || seen.has(trimmed) || !current.includes(trimmed)) continue\n seen.add(trimmed)\n merged.push(trimmed)\n }\n for (const id of current) {\n if (seen.has(id)) continue\n seen.add(id)\n merged.push(id)\n }\n return merged\n}\n\nfunction collectSidebarDefaults(groups: SidebarGroup[]) {\n const groupDefaults = new Map<string, string>()\n const itemDefaults = new Map<string, string>()\n\n const visitItems = (items: SidebarItem[]) => {\n for (const item of items) {\n const baseTitle = item.defaultTitle ?? item.title\n itemDefaults.set(item.href, baseTitle)\n if (item.children && item.children.length > 0) visitItems(item.children)\n }\n }\n\n for (const group of groups) {\n const key = resolveGroupKey(group)\n groupDefaults.set(key, group.defaultName ?? group.name)\n visitItems(group.items)\n }\n\n return { groupDefaults, itemDefaults }\n}\n\n/**\n * Filters groups to include only main sidebar items.\n * Excludes items with pageContext 'settings' or 'profile' from customization.\n * Per SPEC-007: Sidebar customization applies only to the main sidebar.\n */\nfunction filterMainSidebarGroups(groups: SidebarGroup[]): SidebarGroup[] {\n const isMainItem = (item: SidebarItem): boolean => {\n if (item.pageContext && item.pageContext !== 'main') return false\n return true\n }\n\n return groups\n .map((group) => ({\n ...group,\n items: group.items.filter(isMainItem).map((item) => ({\n ...item,\n children: item.children?.filter(isMainItem),\n })),\n }))\n .filter((group) => group.items.length > 0)\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { ChevronDown, ChevronUp, Loader2, CheckCircle, XCircle, X } from "lucide-react";
|
|
5
|
+
import { Button } from "../../primitives/button.js";
|
|
6
|
+
import { Progress } from "../../primitives/progress.js";
|
|
7
|
+
import { cn } from "@open-mercato/shared/lib/utils";
|
|
8
|
+
import { useProgressPoll } from "./useProgressPoll.js";
|
|
9
|
+
import { apiCall } from "../utils/apiCall.js";
|
|
10
|
+
function ProgressTopBar({ className, t }) {
|
|
11
|
+
const { activeJobs, recentlyCompleted, refresh } = useProgressPoll();
|
|
12
|
+
const [expanded, setExpanded] = React.useState(false);
|
|
13
|
+
React.useEffect(() => {
|
|
14
|
+
const saved = localStorage.getItem("om:progress:expanded");
|
|
15
|
+
if (saved === "true") setExpanded(true);
|
|
16
|
+
}, []);
|
|
17
|
+
React.useEffect(() => {
|
|
18
|
+
localStorage.setItem("om:progress:expanded", String(expanded));
|
|
19
|
+
}, [expanded]);
|
|
20
|
+
const hasActiveJobs = activeJobs.length > 0;
|
|
21
|
+
const hasRecentJobs = recentlyCompleted.length > 0;
|
|
22
|
+
if (!hasActiveJobs && !hasRecentJobs) return null;
|
|
23
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("border-b bg-muted/30", className), children: [
|
|
24
|
+
/* @__PURE__ */ jsxs(
|
|
25
|
+
"button",
|
|
26
|
+
{
|
|
27
|
+
type: "button",
|
|
28
|
+
onClick: () => setExpanded(!expanded),
|
|
29
|
+
className: "w-full flex items-center justify-between px-4 py-2 hover:bg-muted/50 transition-colors",
|
|
30
|
+
children: [
|
|
31
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-2 text-sm", children: hasActiveJobs ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
32
|
+
/* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin text-primary" }),
|
|
33
|
+
/* @__PURE__ */ jsx("span", { children: t("progress.activeCount", "{count} operations running", { count: activeJobs.length }) }),
|
|
34
|
+
activeJobs[0] && /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
|
|
35
|
+
"\u2014 ",
|
|
36
|
+
activeJobs[0].name,
|
|
37
|
+
" (",
|
|
38
|
+
activeJobs[0].progressPercent,
|
|
39
|
+
"%)"
|
|
40
|
+
] })
|
|
41
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
42
|
+
/* @__PURE__ */ jsx(CheckCircle, { className: "h-4 w-4 text-green-500" }),
|
|
43
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: t("progress.recentlyCompleted", "{count} operations completed", { count: recentlyCompleted.length }) })
|
|
44
|
+
] }) }),
|
|
45
|
+
expanded ? /* @__PURE__ */ jsx(ChevronUp, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(ChevronDown, { className: "h-4 w-4" })
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
),
|
|
49
|
+
expanded && /* @__PURE__ */ jsxs("div", { className: "px-4 pb-3 space-y-2", children: [
|
|
50
|
+
activeJobs.map((job) => /* @__PURE__ */ jsx(ProgressJobCard, { job, t, onCancel: refresh }, job.id)),
|
|
51
|
+
recentlyCompleted.map((job) => /* @__PURE__ */ jsx(ProgressJobCard, { job, t }, job.id))
|
|
52
|
+
] })
|
|
53
|
+
] });
|
|
54
|
+
}
|
|
55
|
+
function ProgressJobCard({ job, t, onCancel }) {
|
|
56
|
+
const [cancelling, setCancelling] = React.useState(false);
|
|
57
|
+
const handleCancel = async () => {
|
|
58
|
+
if (!job.cancellable || cancelling) return;
|
|
59
|
+
setCancelling(true);
|
|
60
|
+
try {
|
|
61
|
+
await apiCall(`/api/progress/jobs/${job.id}`, { method: "DELETE" });
|
|
62
|
+
onCancel?.();
|
|
63
|
+
} finally {
|
|
64
|
+
setCancelling(false);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
const isActive = job.status === "pending" || job.status === "running";
|
|
68
|
+
const isFailed = job.status === "failed";
|
|
69
|
+
const isCompleted = job.status === "completed";
|
|
70
|
+
return /* @__PURE__ */ jsxs("div", { className: cn(
|
|
71
|
+
"rounded-md border p-3",
|
|
72
|
+
isFailed && "border-destructive/50 bg-destructive/5",
|
|
73
|
+
isCompleted && "border-green-500/50 bg-green-50/50 dark:bg-green-950/20"
|
|
74
|
+
), children: [
|
|
75
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-2", children: [
|
|
76
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
77
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
78
|
+
isActive && /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin text-primary flex-shrink-0" }),
|
|
79
|
+
isCompleted && /* @__PURE__ */ jsx(CheckCircle, { className: "h-4 w-4 text-green-500 flex-shrink-0" }),
|
|
80
|
+
isFailed && /* @__PURE__ */ jsx(XCircle, { className: "h-4 w-4 text-destructive flex-shrink-0" }),
|
|
81
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium truncate", children: job.name })
|
|
82
|
+
] }),
|
|
83
|
+
job.description && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mt-1 truncate", children: job.description }),
|
|
84
|
+
isFailed && job.errorMessage && /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive mt-1", children: job.errorMessage })
|
|
85
|
+
] }),
|
|
86
|
+
isActive && job.cancellable && /* @__PURE__ */ jsx(
|
|
87
|
+
Button,
|
|
88
|
+
{
|
|
89
|
+
variant: "ghost",
|
|
90
|
+
size: "icon",
|
|
91
|
+
onClick: handleCancel,
|
|
92
|
+
disabled: cancelling,
|
|
93
|
+
className: "flex-shrink-0",
|
|
94
|
+
"aria-label": t("progress.actions.cancel", "Cancel"),
|
|
95
|
+
children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
|
|
96
|
+
}
|
|
97
|
+
)
|
|
98
|
+
] }),
|
|
99
|
+
isActive && /* @__PURE__ */ jsxs("div", { className: "mt-2 space-y-1", children: [
|
|
100
|
+
/* @__PURE__ */ jsx(Progress, { value: job.progressPercent, className: "h-2" }),
|
|
101
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-between text-xs text-muted-foreground", children: [
|
|
102
|
+
/* @__PURE__ */ jsx("span", { children: job.totalCount ? `${job.processedCount.toLocaleString()} / ${job.totalCount.toLocaleString()}` : `${job.processedCount.toLocaleString()} ${t("progress.processed", "processed")}` }),
|
|
103
|
+
job.etaSeconds != null && job.etaSeconds > 0 && /* @__PURE__ */ jsx("span", { children: formatEta(job.etaSeconds, t) })
|
|
104
|
+
] })
|
|
105
|
+
] })
|
|
106
|
+
] });
|
|
107
|
+
}
|
|
108
|
+
function formatEta(seconds, t) {
|
|
109
|
+
if (seconds < 60) {
|
|
110
|
+
return t("progress.eta.seconds", "{count}s remaining", { count: seconds });
|
|
111
|
+
}
|
|
112
|
+
if (seconds < 3600) {
|
|
113
|
+
const minutes = Math.ceil(seconds / 60);
|
|
114
|
+
return t("progress.eta.minutes", "{count}m remaining", { count: minutes });
|
|
115
|
+
}
|
|
116
|
+
const hours = Math.floor(seconds / 3600);
|
|
117
|
+
const mins = Math.ceil(seconds % 3600 / 60);
|
|
118
|
+
return t("progress.eta.hoursMinutes", "{hours}h {minutes}m remaining", { hours, minutes: mins });
|
|
119
|
+
}
|
|
120
|
+
export {
|
|
121
|
+
ProgressTopBar
|
|
122
|
+
};
|
|
123
|
+
//# sourceMappingURL=ProgressTopBar.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/backend/progress/ProgressTopBar.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { ChevronDown, ChevronUp, Loader2, CheckCircle, XCircle, X } from 'lucide-react'\nimport { Button } from '../../primitives/button'\nimport { Progress } from '../../primitives/progress'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useProgressPoll } from './useProgressPoll'\nimport type { ProgressJobDto } from './useProgressPoll'\nimport type { TranslateFn } from '@open-mercato/shared/lib/i18n/context'\nimport { apiCall } from '../utils/apiCall'\n\nexport type ProgressTopBarProps = {\n className?: string\n t: TranslateFn\n}\n\nexport function ProgressTopBar({ className, t }: ProgressTopBarProps) {\n const { activeJobs, recentlyCompleted, refresh } = useProgressPoll()\n const [expanded, setExpanded] = React.useState(false)\n\n React.useEffect(() => {\n const saved = localStorage.getItem('om:progress:expanded')\n if (saved === 'true') setExpanded(true)\n }, [])\n\n React.useEffect(() => {\n localStorage.setItem('om:progress:expanded', String(expanded))\n }, [expanded])\n\n const hasActiveJobs = activeJobs.length > 0\n const hasRecentJobs = recentlyCompleted.length > 0\n\n if (!hasActiveJobs && !hasRecentJobs) return null\n\n return (\n <div className={cn('border-b bg-muted/30', className)}>\n <button\n type=\"button\"\n onClick={() => setExpanded(!expanded)}\n className=\"w-full flex items-center justify-between px-4 py-2 hover:bg-muted/50 transition-colors\"\n >\n <div className=\"flex items-center gap-2 text-sm\">\n {hasActiveJobs ? (\n <>\n <Loader2 className=\"h-4 w-4 animate-spin text-primary\" />\n <span>\n {t('progress.activeCount', '{count} operations running', { count: activeJobs.length })}\n </span>\n {activeJobs[0] && (\n <span className=\"text-muted-foreground\">\n \u2014 {activeJobs[0].name} ({activeJobs[0].progressPercent}%)\n </span>\n )}\n </>\n ) : (\n <>\n <CheckCircle className=\"h-4 w-4 text-green-500\" />\n <span className=\"text-muted-foreground\">\n {t('progress.recentlyCompleted', '{count} operations completed', { count: recentlyCompleted.length })}\n </span>\n </>\n )}\n </div>\n {expanded ? (\n <ChevronUp className=\"h-4 w-4\" />\n ) : (\n <ChevronDown className=\"h-4 w-4\" />\n )}\n </button>\n\n {expanded && (\n <div className=\"px-4 pb-3 space-y-2\">\n {activeJobs.map((job) => (\n <ProgressJobCard key={job.id} job={job} t={t} onCancel={refresh} />\n ))}\n {recentlyCompleted.map((job) => (\n <ProgressJobCard key={job.id} job={job} t={t} />\n ))}\n </div>\n )}\n </div>\n )\n}\n\nfunction ProgressJobCard({ job, t, onCancel }: { job: ProgressJobDto; t: TranslateFn; onCancel?: () => void }) {\n const [cancelling, setCancelling] = React.useState(false)\n\n const handleCancel = async () => {\n if (!job.cancellable || cancelling) return\n setCancelling(true)\n try {\n await apiCall(`/api/progress/jobs/${job.id}`, { method: 'DELETE' })\n onCancel?.()\n } finally {\n setCancelling(false)\n }\n }\n\n const isActive = job.status === 'pending' || job.status === 'running'\n const isFailed = job.status === 'failed'\n const isCompleted = job.status === 'completed'\n\n return (\n <div className={cn(\n 'rounded-md border p-3',\n isFailed && 'border-destructive/50 bg-destructive/5',\n isCompleted && 'border-green-500/50 bg-green-50/50 dark:bg-green-950/20',\n )}>\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2\">\n {isActive && <Loader2 className=\"h-4 w-4 animate-spin text-primary flex-shrink-0\" />}\n {isCompleted && <CheckCircle className=\"h-4 w-4 text-green-500 flex-shrink-0\" />}\n {isFailed && <XCircle className=\"h-4 w-4 text-destructive flex-shrink-0\" />}\n <span className=\"font-medium truncate\">{job.name}</span>\n </div>\n\n {job.description && (\n <p className=\"text-sm text-muted-foreground mt-1 truncate\">{job.description}</p>\n )}\n\n {isFailed && job.errorMessage && (\n <p className=\"text-sm text-destructive mt-1\">{job.errorMessage}</p>\n )}\n </div>\n\n {isActive && job.cancellable && (\n <Button\n variant=\"ghost\"\n size=\"icon\"\n onClick={handleCancel}\n disabled={cancelling}\n className=\"flex-shrink-0\"\n aria-label={t('progress.actions.cancel', 'Cancel')}\n >\n <X className=\"h-4 w-4\" />\n </Button>\n )}\n </div>\n\n {isActive && (\n <div className=\"mt-2 space-y-1\">\n <Progress value={job.progressPercent} className=\"h-2\" />\n <div className=\"flex justify-between text-xs text-muted-foreground\">\n <span>\n {job.totalCount\n ? `${job.processedCount.toLocaleString()} / ${job.totalCount.toLocaleString()}`\n : `${job.processedCount.toLocaleString()} ${t('progress.processed', 'processed')}`\n }\n </span>\n {job.etaSeconds != null && job.etaSeconds > 0 && (\n <span>{formatEta(job.etaSeconds, t)}</span>\n )}\n </div>\n </div>\n )}\n </div>\n )\n}\n\nfunction formatEta(seconds: number, t: TranslateFn): string {\n if (seconds < 60) {\n return t('progress.eta.seconds', '{count}s remaining', { count: seconds })\n }\n if (seconds < 3600) {\n const minutes = Math.ceil(seconds / 60)\n return t('progress.eta.minutes', '{count}m remaining', { count: minutes })\n }\n const hours = Math.floor(seconds / 3600)\n const mins = Math.ceil((seconds % 3600) / 60)\n return t('progress.eta.hoursMinutes', '{hours}h {minutes}m remaining', { hours, minutes: mins })\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { apiCall } from "../utils/apiCall.js";
|
|
4
|
+
import {
|
|
5
|
+
subscribeProgressUpdate,
|
|
6
|
+
subscribeProgressComplete
|
|
7
|
+
} from "@open-mercato/shared/lib/frontend/progressEvents";
|
|
8
|
+
const POLL_INTERVAL = 5e3;
|
|
9
|
+
function useProgressPoll() {
|
|
10
|
+
const [activeJobs, setActiveJobs] = React.useState([]);
|
|
11
|
+
const [recentlyCompleted, setRecentlyCompleted] = React.useState([]);
|
|
12
|
+
const [isLoading, setIsLoading] = React.useState(true);
|
|
13
|
+
const [error, setError] = React.useState(null);
|
|
14
|
+
const fetchJobs = React.useCallback(async () => {
|
|
15
|
+
try {
|
|
16
|
+
const result = await apiCall(
|
|
17
|
+
"/api/progress/active"
|
|
18
|
+
);
|
|
19
|
+
if (result.ok && result.result) {
|
|
20
|
+
setActiveJobs(result.result.active);
|
|
21
|
+
setRecentlyCompleted(result.result.recentlyCompleted);
|
|
22
|
+
setError(null);
|
|
23
|
+
}
|
|
24
|
+
} catch (err) {
|
|
25
|
+
setError(err instanceof Error ? err.message : "Failed to fetch progress");
|
|
26
|
+
} finally {
|
|
27
|
+
setIsLoading(false);
|
|
28
|
+
}
|
|
29
|
+
}, []);
|
|
30
|
+
const refresh = React.useCallback(() => {
|
|
31
|
+
fetchJobs();
|
|
32
|
+
}, [fetchJobs]);
|
|
33
|
+
React.useEffect(() => {
|
|
34
|
+
fetchJobs();
|
|
35
|
+
let interval = setInterval(fetchJobs, POLL_INTERVAL);
|
|
36
|
+
const onVisibilityChange = () => {
|
|
37
|
+
if (document.hidden) {
|
|
38
|
+
if (interval) {
|
|
39
|
+
clearInterval(interval);
|
|
40
|
+
interval = null;
|
|
41
|
+
}
|
|
42
|
+
} else {
|
|
43
|
+
fetchJobs();
|
|
44
|
+
interval = setInterval(fetchJobs, POLL_INTERVAL);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
document.addEventListener("visibilitychange", onVisibilityChange);
|
|
48
|
+
return () => {
|
|
49
|
+
if (interval) clearInterval(interval);
|
|
50
|
+
document.removeEventListener("visibilitychange", onVisibilityChange);
|
|
51
|
+
};
|
|
52
|
+
}, [fetchJobs]);
|
|
53
|
+
React.useEffect(() => {
|
|
54
|
+
const unsubUpdate = subscribeProgressUpdate(() => refresh());
|
|
55
|
+
const unsubComplete = subscribeProgressComplete(() => refresh());
|
|
56
|
+
return () => {
|
|
57
|
+
unsubUpdate();
|
|
58
|
+
unsubComplete();
|
|
59
|
+
};
|
|
60
|
+
}, [refresh]);
|
|
61
|
+
return { activeJobs, recentlyCompleted, isLoading, error, refresh };
|
|
62
|
+
}
|
|
63
|
+
export {
|
|
64
|
+
useProgressPoll
|
|
65
|
+
};
|
|
66
|
+
//# sourceMappingURL=useProgressPoll.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/backend/progress/useProgressPoll.ts"],
|
|
4
|
+
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { apiCall } from '../utils/apiCall'\nimport {\n subscribeProgressUpdate,\n subscribeProgressComplete,\n} from '@open-mercato/shared/lib/frontend/progressEvents'\n\nexport type ProgressJobDto = {\n id: string\n jobType: string\n name: string\n description?: string | null\n status: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled'\n progressPercent: number\n processedCount: number\n totalCount?: number | null\n etaSeconds?: number | null\n cancellable: boolean\n startedAt?: string | null\n finishedAt?: string | null\n errorMessage?: string | null\n}\n\nexport type UseProgressPollResult = {\n activeJobs: ProgressJobDto[]\n recentlyCompleted: ProgressJobDto[]\n isLoading: boolean\n error: string | null\n refresh: () => void\n}\n\nconst POLL_INTERVAL = 5000\n\nexport function useProgressPoll(): UseProgressPollResult {\n const [activeJobs, setActiveJobs] = React.useState<ProgressJobDto[]>([])\n const [recentlyCompleted, setRecentlyCompleted] = React.useState<ProgressJobDto[]>([])\n const [isLoading, setIsLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n\n const fetchJobs = React.useCallback(async () => {\n try {\n const result = await apiCall<{ active: ProgressJobDto[]; recentlyCompleted: ProgressJobDto[] }>(\n '/api/progress/active'\n )\n if (result.ok && result.result) {\n setActiveJobs(result.result.active)\n setRecentlyCompleted(result.result.recentlyCompleted)\n setError(null)\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to fetch progress')\n } finally {\n setIsLoading(false)\n }\n }, [])\n\n const refresh = React.useCallback(() => {\n fetchJobs()\n }, [fetchJobs])\n\n React.useEffect(() => {\n fetchJobs()\n let interval: ReturnType<typeof setInterval> | null = setInterval(fetchJobs, POLL_INTERVAL)\n\n const onVisibilityChange = () => {\n if (document.hidden) {\n if (interval) {\n clearInterval(interval)\n interval = null\n }\n } else {\n fetchJobs()\n interval = setInterval(fetchJobs, POLL_INTERVAL)\n }\n }\n\n document.addEventListener('visibilitychange', onVisibilityChange)\n return () => {\n if (interval) clearInterval(interval)\n document.removeEventListener('visibilitychange', onVisibilityChange)\n }\n }, [fetchJobs])\n\n React.useEffect(() => {\n const unsubUpdate = subscribeProgressUpdate(() => refresh())\n const unsubComplete = subscribeProgressComplete(() => refresh())\n return () => {\n unsubUpdate()\n unsubComplete()\n }\n }, [refresh])\n\n return { activeJobs, recentlyCompleted, isLoading, error, refresh }\n}\n"],
|
|
5
|
+
"mappings": ";AACA,YAAY,WAAW;AACvB,SAAS,eAAe;AACxB;AAAA,EACE;AAAA,EACA;AAAA,OACK;AA0BP,MAAM,gBAAgB;AAEf,SAAS,kBAAyC;AACvD,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAA2B,CAAC,CAAC;AACvE,QAAM,CAAC,mBAAmB,oBAAoB,IAAI,MAAM,SAA2B,CAAC,CAAC;AACrF,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAS,IAAI;AACrD,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAwB,IAAI;AAE5D,QAAM,YAAY,MAAM,YAAY,YAAY;AAC9C,QAAI;AACF,YAAM,SAAS,MAAM;AAAA,QACnB;AAAA,MACF;AACA,UAAI,OAAO,MAAM,OAAO,QAAQ;AAC9B,sBAAc,OAAO,OAAO,MAAM;AAClC,6BAAqB,OAAO,OAAO,iBAAiB;AACpD,iBAAS,IAAI;AAAA,MACf;AAAA,IACF,SAAS,KAAK;AACZ,eAAS,eAAe,QAAQ,IAAI,UAAU,0BAA0B;AAAA,IAC1E,UAAE;AACA,mBAAa,KAAK;AAAA,IACpB;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,QAAM,UAAU,MAAM,YAAY,MAAM;AACtC,cAAU;AAAA,EACZ,GAAG,CAAC,SAAS,CAAC;AAEd,QAAM,UAAU,MAAM;AACpB,cAAU;AACV,QAAI,WAAkD,YAAY,WAAW,aAAa;AAE1F,UAAM,qBAAqB,MAAM;AAC/B,UAAI,SAAS,QAAQ;AACnB,YAAI,UAAU;AACZ,wBAAc,QAAQ;AACtB,qBAAW;AAAA,QACb;AAAA,MACF,OAAO;AACL,kBAAU;AACV,mBAAW,YAAY,WAAW,aAAa;AAAA,MACjD;AAAA,IACF;AAEA,aAAS,iBAAiB,oBAAoB,kBAAkB;AAChE,WAAO,MAAM;AACX,UAAI,SAAU,eAAc,QAAQ;AACpC,eAAS,oBAAoB,oBAAoB,kBAAkB;AAAA,IACrE;AAAA,EACF,GAAG,CAAC,SAAS,CAAC;AAEd,QAAM,UAAU,MAAM;AACpB,UAAM,cAAc,wBAAwB,MAAM,QAAQ,CAAC;AAC3D,UAAM,gBAAgB,0BAA0B,MAAM,QAAQ,CAAC;AAC/D,WAAO,MAAM;AACX,kBAAY;AACZ,oBAAc;AAAA,IAChB;AAAA,EACF,GAAG,CAAC,OAAO,CAAC;AAEZ,SAAO,EAAE,YAAY,mBAAmB,WAAW,OAAO,QAAQ;AACpE;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/index.ts"],
|
|
4
|
-
"sourcesContent": ["export * from './theme/ThemeProvider'\nexport * from './theme/ThemeToggle'\nexport * from './theme/QueryProvider'\nexport * from './backend/AppShell'\nexport * from './backend/Page'\nexport * from './backend/DataTable'\nexport * from './backend/FilterBar'\nexport * from './backend/ValueIcons'\nexport * from './backend/confirm-dialog'\nexport * from './backend/UserMenu'\nexport * from './backend/RowActions'\nexport * from './backend/utils/nav'\nexport * from './backend/CrudForm'\nexport * from './backend/JsonBuilder'\nexport * from './backend/detail'\nexport * from './backend/TruncatedCell'\nexport * from './backend/schedule'\n\nexport * from './backend/inputs'\nexport * from './backend/ContextHelp'\nexport * from './backend/dashboard'\nexport * from './frontend/Layout'\nexport * from './frontend/AuthFooter'\nexport * from './frontend/LanguageSwitcher'\nexport * from './primitives/button'\nexport * from './primitives/icon-button' \nexport * from './primitives/label'\nexport * from './primitives/separator'\nexport * from './primitives/spinner'\nexport * from './primitives/tabs'\nexport * from './primitives/DataLoader'\nexport * from './primitives/table'\nexport * from './primitives/Notice'\nexport * from './primitives/ErrorNotice'\nexport * from './primitives/dialog'\n"],
|
|
5
|
-
"mappings": "AAAA,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAEd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;",
|
|
4
|
+
"sourcesContent": ["export * from './theme/ThemeProvider'\nexport * from './theme/ThemeToggle'\nexport * from './theme/QueryProvider'\nexport * from './backend/AppShell'\nexport * from './backend/Page'\nexport * from './backend/DataTable'\nexport * from './backend/FilterBar'\nexport * from './backend/ValueIcons'\nexport * from './backend/confirm-dialog'\nexport * from './backend/UserMenu'\nexport * from './backend/RowActions'\nexport * from './backend/utils/nav'\nexport * from './backend/CrudForm'\nexport * from './backend/JsonBuilder'\nexport * from './backend/detail'\nexport * from './backend/TruncatedCell'\nexport * from './backend/schedule'\n\nexport * from './backend/inputs'\nexport * from './backend/ContextHelp'\nexport * from './backend/dashboard'\nexport * from './frontend/Layout'\nexport * from './frontend/AuthFooter'\nexport * from './frontend/LanguageSwitcher'\nexport * from './primitives/button'\nexport * from './primitives/icon-button' \nexport * from './primitives/label'\nexport * from './primitives/separator'\nexport * from './primitives/spinner'\nexport * from './primitives/tabs'\nexport * from './primitives/DataLoader'\nexport * from './primitives/table'\nexport * from './primitives/Notice'\nexport * from './primitives/ErrorNotice'\nexport * from './primitives/dialog'\nexport * from './primitives/progress'\n"],
|
|
5
|
+
"mappings": "AAAA,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAEd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { cn } from "@open-mercato/shared/lib/utils";
|
|
4
|
+
function Progress({ value, max = 100, className, ...props }) {
|
|
5
|
+
const percentage = Math.min(100, Math.max(0, Math.round(value / max * 100)));
|
|
6
|
+
return /* @__PURE__ */ jsx(
|
|
7
|
+
"div",
|
|
8
|
+
{
|
|
9
|
+
role: "progressbar",
|
|
10
|
+
"aria-valuenow": value,
|
|
11
|
+
"aria-valuemin": 0,
|
|
12
|
+
"aria-valuemax": max,
|
|
13
|
+
className: cn("relative w-full overflow-hidden rounded-full bg-secondary", className),
|
|
14
|
+
...props,
|
|
15
|
+
children: /* @__PURE__ */ jsx(
|
|
16
|
+
"div",
|
|
17
|
+
{
|
|
18
|
+
className: "h-full bg-primary transition-all duration-300 ease-in-out",
|
|
19
|
+
style: { width: `${percentage}%` }
|
|
20
|
+
}
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
export {
|
|
26
|
+
Progress
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=progress.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/primitives/progress.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { cn } from '@open-mercato/shared/lib/utils'\n\nexport interface ProgressProps extends React.HTMLAttributes<HTMLDivElement> {\n value: number\n max?: number\n}\n\nexport function Progress({ value, max = 100, className, ...props }: ProgressProps) {\n const percentage = Math.min(100, Math.max(0, Math.round((value / max) * 100)))\n\n return (\n <div\n role=\"progressbar\"\n aria-valuenow={value}\n aria-valuemin={0}\n aria-valuemax={max}\n className={cn('relative w-full overflow-hidden rounded-full bg-secondary', className)}\n {...props}\n >\n <div\n className=\"h-full bg-primary transition-all duration-300 ease-in-out\"\n style={{ width: `${percentage}%` }}\n />\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": ";AAqBM;AAnBN,SAAS,UAAU;AAOZ,SAAS,SAAS,EAAE,OAAO,MAAM,KAAK,WAAW,GAAG,MAAM,GAAkB;AACjF,QAAM,aAAa,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,KAAK,MAAO,QAAQ,MAAO,GAAG,CAAC,CAAC;AAE7E,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,iBAAe;AAAA,MACf,iBAAe;AAAA,MACf,iBAAe;AAAA,MACf,WAAW,GAAG,6DAA6D,SAAS;AAAA,MACnF,GAAG;AAAA,MAEJ;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,OAAO,EAAE,OAAO,GAAG,UAAU,IAAI;AAAA;AAAA,MACnC;AAAA;AAAA,EACF;AAEJ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/ui",
|
|
3
|
-
"version": "0.4.5-develop-
|
|
3
|
+
"version": "0.4.5-develop-8a56591995",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -112,7 +112,7 @@
|
|
|
112
112
|
}
|
|
113
113
|
},
|
|
114
114
|
"dependencies": {
|
|
115
|
-
"@open-mercato/shared": "0.4.5-develop-
|
|
115
|
+
"@open-mercato/shared": "0.4.5-develop-8a56591995",
|
|
116
116
|
"@radix-ui/react-popover": "^1.1.6",
|
|
117
117
|
"@radix-ui/react-tooltip": "^1.2.8",
|
|
118
118
|
"date-fns": "^4.1.0",
|
package/src/backend/AppShell.tsx
CHANGED
|
@@ -10,6 +10,7 @@ import { FlashMessages } from './FlashMessages'
|
|
|
10
10
|
import { usePathname, useSearchParams } from 'next/navigation'
|
|
11
11
|
import { apiCall } from './utils/apiCall'
|
|
12
12
|
import { LastOperationBanner } from './operations/LastOperationBanner'
|
|
13
|
+
import { ProgressTopBar } from './progress/ProgressTopBar'
|
|
13
14
|
import { UpgradeActionBanner } from './upgrades/UpgradeActionBanner'
|
|
14
15
|
import { PartialIndexBanner } from './indexes/PartialIndexBanner'
|
|
15
16
|
import { useLocale, useT } from '@open-mercato/shared/lib/i18n/context'
|
|
@@ -1210,6 +1211,7 @@ export function AppShell({ productName, email, groups, rightHeaderSlot, children
|
|
|
1210
1211
|
)}
|
|
1211
1212
|
</div>
|
|
1212
1213
|
</header>
|
|
1214
|
+
<ProgressTopBar t={t} className="sticky top-0 z-10" />
|
|
1213
1215
|
<main className="flex-1 p-4 lg:p-6">
|
|
1214
1216
|
<InjectionSpot spotId={BACKEND_LAYOUT_TOP_INJECTION_SPOT_ID} context={injectionContext} />
|
|
1215
1217
|
<FlashMessages />
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
import * as React from 'react'
|
|
3
|
+
import { ChevronDown, ChevronUp, Loader2, CheckCircle, XCircle, X } from 'lucide-react'
|
|
4
|
+
import { Button } from '../../primitives/button'
|
|
5
|
+
import { Progress } from '../../primitives/progress'
|
|
6
|
+
import { cn } from '@open-mercato/shared/lib/utils'
|
|
7
|
+
import { useProgressPoll } from './useProgressPoll'
|
|
8
|
+
import type { ProgressJobDto } from './useProgressPoll'
|
|
9
|
+
import type { TranslateFn } from '@open-mercato/shared/lib/i18n/context'
|
|
10
|
+
import { apiCall } from '../utils/apiCall'
|
|
11
|
+
|
|
12
|
+
export type ProgressTopBarProps = {
|
|
13
|
+
className?: string
|
|
14
|
+
t: TranslateFn
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function ProgressTopBar({ className, t }: ProgressTopBarProps) {
|
|
18
|
+
const { activeJobs, recentlyCompleted, refresh } = useProgressPoll()
|
|
19
|
+
const [expanded, setExpanded] = React.useState(false)
|
|
20
|
+
|
|
21
|
+
React.useEffect(() => {
|
|
22
|
+
const saved = localStorage.getItem('om:progress:expanded')
|
|
23
|
+
if (saved === 'true') setExpanded(true)
|
|
24
|
+
}, [])
|
|
25
|
+
|
|
26
|
+
React.useEffect(() => {
|
|
27
|
+
localStorage.setItem('om:progress:expanded', String(expanded))
|
|
28
|
+
}, [expanded])
|
|
29
|
+
|
|
30
|
+
const hasActiveJobs = activeJobs.length > 0
|
|
31
|
+
const hasRecentJobs = recentlyCompleted.length > 0
|
|
32
|
+
|
|
33
|
+
if (!hasActiveJobs && !hasRecentJobs) return null
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div className={cn('border-b bg-muted/30', className)}>
|
|
37
|
+
<button
|
|
38
|
+
type="button"
|
|
39
|
+
onClick={() => setExpanded(!expanded)}
|
|
40
|
+
className="w-full flex items-center justify-between px-4 py-2 hover:bg-muted/50 transition-colors"
|
|
41
|
+
>
|
|
42
|
+
<div className="flex items-center gap-2 text-sm">
|
|
43
|
+
{hasActiveJobs ? (
|
|
44
|
+
<>
|
|
45
|
+
<Loader2 className="h-4 w-4 animate-spin text-primary" />
|
|
46
|
+
<span>
|
|
47
|
+
{t('progress.activeCount', '{count} operations running', { count: activeJobs.length })}
|
|
48
|
+
</span>
|
|
49
|
+
{activeJobs[0] && (
|
|
50
|
+
<span className="text-muted-foreground">
|
|
51
|
+
— {activeJobs[0].name} ({activeJobs[0].progressPercent}%)
|
|
52
|
+
</span>
|
|
53
|
+
)}
|
|
54
|
+
</>
|
|
55
|
+
) : (
|
|
56
|
+
<>
|
|
57
|
+
<CheckCircle className="h-4 w-4 text-green-500" />
|
|
58
|
+
<span className="text-muted-foreground">
|
|
59
|
+
{t('progress.recentlyCompleted', '{count} operations completed', { count: recentlyCompleted.length })}
|
|
60
|
+
</span>
|
|
61
|
+
</>
|
|
62
|
+
)}
|
|
63
|
+
</div>
|
|
64
|
+
{expanded ? (
|
|
65
|
+
<ChevronUp className="h-4 w-4" />
|
|
66
|
+
) : (
|
|
67
|
+
<ChevronDown className="h-4 w-4" />
|
|
68
|
+
)}
|
|
69
|
+
</button>
|
|
70
|
+
|
|
71
|
+
{expanded && (
|
|
72
|
+
<div className="px-4 pb-3 space-y-2">
|
|
73
|
+
{activeJobs.map((job) => (
|
|
74
|
+
<ProgressJobCard key={job.id} job={job} t={t} onCancel={refresh} />
|
|
75
|
+
))}
|
|
76
|
+
{recentlyCompleted.map((job) => (
|
|
77
|
+
<ProgressJobCard key={job.id} job={job} t={t} />
|
|
78
|
+
))}
|
|
79
|
+
</div>
|
|
80
|
+
)}
|
|
81
|
+
</div>
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function ProgressJobCard({ job, t, onCancel }: { job: ProgressJobDto; t: TranslateFn; onCancel?: () => void }) {
|
|
86
|
+
const [cancelling, setCancelling] = React.useState(false)
|
|
87
|
+
|
|
88
|
+
const handleCancel = async () => {
|
|
89
|
+
if (!job.cancellable || cancelling) return
|
|
90
|
+
setCancelling(true)
|
|
91
|
+
try {
|
|
92
|
+
await apiCall(`/api/progress/jobs/${job.id}`, { method: 'DELETE' })
|
|
93
|
+
onCancel?.()
|
|
94
|
+
} finally {
|
|
95
|
+
setCancelling(false)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const isActive = job.status === 'pending' || job.status === 'running'
|
|
100
|
+
const isFailed = job.status === 'failed'
|
|
101
|
+
const isCompleted = job.status === 'completed'
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<div className={cn(
|
|
105
|
+
'rounded-md border p-3',
|
|
106
|
+
isFailed && 'border-destructive/50 bg-destructive/5',
|
|
107
|
+
isCompleted && 'border-green-500/50 bg-green-50/50 dark:bg-green-950/20',
|
|
108
|
+
)}>
|
|
109
|
+
<div className="flex items-start justify-between gap-2">
|
|
110
|
+
<div className="flex-1 min-w-0">
|
|
111
|
+
<div className="flex items-center gap-2">
|
|
112
|
+
{isActive && <Loader2 className="h-4 w-4 animate-spin text-primary flex-shrink-0" />}
|
|
113
|
+
{isCompleted && <CheckCircle className="h-4 w-4 text-green-500 flex-shrink-0" />}
|
|
114
|
+
{isFailed && <XCircle className="h-4 w-4 text-destructive flex-shrink-0" />}
|
|
115
|
+
<span className="font-medium truncate">{job.name}</span>
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
{job.description && (
|
|
119
|
+
<p className="text-sm text-muted-foreground mt-1 truncate">{job.description}</p>
|
|
120
|
+
)}
|
|
121
|
+
|
|
122
|
+
{isFailed && job.errorMessage && (
|
|
123
|
+
<p className="text-sm text-destructive mt-1">{job.errorMessage}</p>
|
|
124
|
+
)}
|
|
125
|
+
</div>
|
|
126
|
+
|
|
127
|
+
{isActive && job.cancellable && (
|
|
128
|
+
<Button
|
|
129
|
+
variant="ghost"
|
|
130
|
+
size="icon"
|
|
131
|
+
onClick={handleCancel}
|
|
132
|
+
disabled={cancelling}
|
|
133
|
+
className="flex-shrink-0"
|
|
134
|
+
aria-label={t('progress.actions.cancel', 'Cancel')}
|
|
135
|
+
>
|
|
136
|
+
<X className="h-4 w-4" />
|
|
137
|
+
</Button>
|
|
138
|
+
)}
|
|
139
|
+
</div>
|
|
140
|
+
|
|
141
|
+
{isActive && (
|
|
142
|
+
<div className="mt-2 space-y-1">
|
|
143
|
+
<Progress value={job.progressPercent} className="h-2" />
|
|
144
|
+
<div className="flex justify-between text-xs text-muted-foreground">
|
|
145
|
+
<span>
|
|
146
|
+
{job.totalCount
|
|
147
|
+
? `${job.processedCount.toLocaleString()} / ${job.totalCount.toLocaleString()}`
|
|
148
|
+
: `${job.processedCount.toLocaleString()} ${t('progress.processed', 'processed')}`
|
|
149
|
+
}
|
|
150
|
+
</span>
|
|
151
|
+
{job.etaSeconds != null && job.etaSeconds > 0 && (
|
|
152
|
+
<span>{formatEta(job.etaSeconds, t)}</span>
|
|
153
|
+
)}
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
156
|
+
)}
|
|
157
|
+
</div>
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function formatEta(seconds: number, t: TranslateFn): string {
|
|
162
|
+
if (seconds < 60) {
|
|
163
|
+
return t('progress.eta.seconds', '{count}s remaining', { count: seconds })
|
|
164
|
+
}
|
|
165
|
+
if (seconds < 3600) {
|
|
166
|
+
const minutes = Math.ceil(seconds / 60)
|
|
167
|
+
return t('progress.eta.minutes', '{count}m remaining', { count: minutes })
|
|
168
|
+
}
|
|
169
|
+
const hours = Math.floor(seconds / 3600)
|
|
170
|
+
const mins = Math.ceil((seconds % 3600) / 60)
|
|
171
|
+
return t('progress.eta.hoursMinutes', '{hours}h {minutes}m remaining', { hours, minutes: mins })
|
|
172
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
import * as React from 'react'
|
|
3
|
+
import { apiCall } from '../utils/apiCall'
|
|
4
|
+
import {
|
|
5
|
+
subscribeProgressUpdate,
|
|
6
|
+
subscribeProgressComplete,
|
|
7
|
+
} from '@open-mercato/shared/lib/frontend/progressEvents'
|
|
8
|
+
|
|
9
|
+
export type ProgressJobDto = {
|
|
10
|
+
id: string
|
|
11
|
+
jobType: string
|
|
12
|
+
name: string
|
|
13
|
+
description?: string | null
|
|
14
|
+
status: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled'
|
|
15
|
+
progressPercent: number
|
|
16
|
+
processedCount: number
|
|
17
|
+
totalCount?: number | null
|
|
18
|
+
etaSeconds?: number | null
|
|
19
|
+
cancellable: boolean
|
|
20
|
+
startedAt?: string | null
|
|
21
|
+
finishedAt?: string | null
|
|
22
|
+
errorMessage?: string | null
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type UseProgressPollResult = {
|
|
26
|
+
activeJobs: ProgressJobDto[]
|
|
27
|
+
recentlyCompleted: ProgressJobDto[]
|
|
28
|
+
isLoading: boolean
|
|
29
|
+
error: string | null
|
|
30
|
+
refresh: () => void
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const POLL_INTERVAL = 5000
|
|
34
|
+
|
|
35
|
+
export function useProgressPoll(): UseProgressPollResult {
|
|
36
|
+
const [activeJobs, setActiveJobs] = React.useState<ProgressJobDto[]>([])
|
|
37
|
+
const [recentlyCompleted, setRecentlyCompleted] = React.useState<ProgressJobDto[]>([])
|
|
38
|
+
const [isLoading, setIsLoading] = React.useState(true)
|
|
39
|
+
const [error, setError] = React.useState<string | null>(null)
|
|
40
|
+
|
|
41
|
+
const fetchJobs = React.useCallback(async () => {
|
|
42
|
+
try {
|
|
43
|
+
const result = await apiCall<{ active: ProgressJobDto[]; recentlyCompleted: ProgressJobDto[] }>(
|
|
44
|
+
'/api/progress/active'
|
|
45
|
+
)
|
|
46
|
+
if (result.ok && result.result) {
|
|
47
|
+
setActiveJobs(result.result.active)
|
|
48
|
+
setRecentlyCompleted(result.result.recentlyCompleted)
|
|
49
|
+
setError(null)
|
|
50
|
+
}
|
|
51
|
+
} catch (err) {
|
|
52
|
+
setError(err instanceof Error ? err.message : 'Failed to fetch progress')
|
|
53
|
+
} finally {
|
|
54
|
+
setIsLoading(false)
|
|
55
|
+
}
|
|
56
|
+
}, [])
|
|
57
|
+
|
|
58
|
+
const refresh = React.useCallback(() => {
|
|
59
|
+
fetchJobs()
|
|
60
|
+
}, [fetchJobs])
|
|
61
|
+
|
|
62
|
+
React.useEffect(() => {
|
|
63
|
+
fetchJobs()
|
|
64
|
+
let interval: ReturnType<typeof setInterval> | null = setInterval(fetchJobs, POLL_INTERVAL)
|
|
65
|
+
|
|
66
|
+
const onVisibilityChange = () => {
|
|
67
|
+
if (document.hidden) {
|
|
68
|
+
if (interval) {
|
|
69
|
+
clearInterval(interval)
|
|
70
|
+
interval = null
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
fetchJobs()
|
|
74
|
+
interval = setInterval(fetchJobs, POLL_INTERVAL)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
document.addEventListener('visibilitychange', onVisibilityChange)
|
|
79
|
+
return () => {
|
|
80
|
+
if (interval) clearInterval(interval)
|
|
81
|
+
document.removeEventListener('visibilitychange', onVisibilityChange)
|
|
82
|
+
}
|
|
83
|
+
}, [fetchJobs])
|
|
84
|
+
|
|
85
|
+
React.useEffect(() => {
|
|
86
|
+
const unsubUpdate = subscribeProgressUpdate(() => refresh())
|
|
87
|
+
const unsubComplete = subscribeProgressComplete(() => refresh())
|
|
88
|
+
return () => {
|
|
89
|
+
unsubUpdate()
|
|
90
|
+
unsubComplete()
|
|
91
|
+
}
|
|
92
|
+
}, [refresh])
|
|
93
|
+
|
|
94
|
+
return { activeJobs, recentlyCompleted, isLoading, error, refresh }
|
|
95
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
import * as React from 'react'
|
|
3
|
+
import { cn } from '@open-mercato/shared/lib/utils'
|
|
4
|
+
|
|
5
|
+
export interface ProgressProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
6
|
+
value: number
|
|
7
|
+
max?: number
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function Progress({ value, max = 100, className, ...props }: ProgressProps) {
|
|
11
|
+
const percentage = Math.min(100, Math.max(0, Math.round((value / max) * 100)))
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<div
|
|
15
|
+
role="progressbar"
|
|
16
|
+
aria-valuenow={value}
|
|
17
|
+
aria-valuemin={0}
|
|
18
|
+
aria-valuemax={max}
|
|
19
|
+
className={cn('relative w-full overflow-hidden rounded-full bg-secondary', className)}
|
|
20
|
+
{...props}
|
|
21
|
+
>
|
|
22
|
+
<div
|
|
23
|
+
className="h-full bg-primary transition-all duration-300 ease-in-out"
|
|
24
|
+
style={{ width: `${percentage}%` }}
|
|
25
|
+
/>
|
|
26
|
+
</div>
|
|
27
|
+
)
|
|
28
|
+
}
|