Loading portal-gui/src/app/config/navigation.ts +7 −1 Changes for portal-gui/src/app/config/navigation.ts: 7 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -114,6 +114,7 @@ export function canSeeNavItem( } export function isActivePath(pathname: string, to: string) { pathname = normalizePathname(pathname); if (to === '/') { return pathname === '/'; } Loading @@ -122,10 +123,14 @@ export function isActivePath(pathname: string, to: string) { } export function getActiveNavItem(pathname: string): NavItem | undefined { const navPath = findNavPath(mainNavItems, pathname); const navPath = findNavPath(mainNavItems, normalizePathname(pathname)); return navPath?.at(-1); } export function normalizePathname(pathname: string) { return pathname.replace(/\/+$/, '') || '/'; } function findNavPath(items: NavItem[], pathname: string): NavItem[] | null { for (const item of items) { if (item.to === pathname) { Loading @@ -145,6 +150,7 @@ export function getBreadcrumbItems( pathname: string, api?: Pick<Api, 'id' | 'title'> | null, ): BreadcrumbItem[] { pathname = normalizePathname(pathname); // Detail pages such as /apis/:id are not in the nav, so walk up to the // nearest page that is. let navPath: NavItem[] | null = null; Loading portal-gui/src/components/common/app-breadcrumbs.tsx +3 −2 Changes for portal-gui/src/components/common/app-breadcrumbs.tsx: 3 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -9,11 +9,12 @@ import { BreadcrumbSeparator, } from '@/components/ui/breadcrumb'; import { getBreadcrumbItems } from '@/app/config/navigation'; import { getBreadcrumbItems, normalizePathname } from '@/app/config/navigation'; import type { Api } from '@/types/api'; export function AppBreadcrumbs({ api }: { api?: Api | null }) { const { pathname } = useLocation(); const activePath = normalizePathname(pathname); const items = getBreadcrumbItems(pathname, api); return ( Loading @@ -21,7 +22,7 @@ export function AppBreadcrumbs({ api }: { api?: Api | null }) { <BreadcrumbList> {items.map((item, index) => { const isLast = index === items.length - 1; const isCurrent = item.to === pathname; const isCurrent = item.to === activePath; return ( <Fragment key={item.to}> Loading portal-gui/src/components/data-table/data-table.tsx +7 −0 Changes for portal-gui/src/components/data-table/data-table.tsx: 7 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -106,6 +106,13 @@ export function DataTable<TData>({ getSortedRowModel: getSortedRowModel(), }); const rows = table.getRowModel().rows; const lastPageIndex = Math.max(0, table.getPageCount() - 1); useEffect(() => { if (pagination.pageIndex > lastPageIndex) { table.setPageIndex(lastPageIndex); } }, [lastPageIndex, pagination.pageIndex, table]); const visibleIds = rows.map((row) => row.id).join(','); useEffect(() => { Loading portal-gui/src/features/apis/api-workspace.tsx +8 −4 Changes for portal-gui/src/features/apis/api-workspace.tsx: 8 added lines, 4 removed lines. Original line number Diff line number Diff line Loading @@ -7,6 +7,7 @@ import { useParams, } from 'react-router-dom'; import { ROUTES } from '@/app/config/route'; import { normalizePathname } from '@/app/config/navigation'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { PageState } from '@/components/common/page-state'; Loading @@ -16,6 +17,7 @@ export function ApiWorkspacePage() { const { id } = useParams<{ id: string }>(); const { api, loading, error } = useOutletContext<ApiState>(); const { pathname } = useLocation(); const activePath = normalizePathname(pathname); const basePath = `${ROUTES.APIS}/${id}`; const hasExplore = basePath === ROUTES.EDGE_APPLICATION_MANAGEMENT; Loading Loading @@ -51,12 +53,14 @@ export function ApiWorkspacePage() { <nav aria-label="API sections" className="flex gap-2"> <Button asChild variant={pathname.endsWith('/overview') ? 'secondary' : 'ghost'} variant={ activePath.endsWith('/overview') ? 'secondary' : 'ghost' } > <Link to={`${basePath}/overview`} aria-current={ pathname.endsWith('/overview') ? 'page' : undefined activePath.endsWith('/overview') ? 'page' : undefined } > <BookOpen aria-hidden /> Loading @@ -67,7 +71,7 @@ export function ApiWorkspacePage() { <Button asChild variant={ pathname.startsWith(`${basePath}/explore`) activePath.startsWith(`${basePath}/explore`) ? 'secondary' : 'ghost' } Loading @@ -75,7 +79,7 @@ export function ApiWorkspacePage() { <Link to={`${basePath}/explore`} aria-current={ pathname.startsWith(`${basePath}/explore`) activePath.startsWith(`${basePath}/explore`) ? 'page' : undefined } Loading portal-gui/src/features/applications/applications.tsx +16 −4 Changes for portal-gui/src/features/applications/applications.tsx: 16 added lines, 4 removed lines. Original line number Diff line number Diff line import { useEffect, useRef } from 'react'; import { Navigate, Outlet, Loading @@ -5,7 +6,10 @@ import { useNavigate, useOutletContext, } from 'react-router-dom'; import { applicationNavItems } from '@/app/config/navigation'; import { applicationNavItems, normalizePathname, } from '@/app/config/navigation'; import { ROUTES } from '@/app/config/route'; import { EmptyState } from '@/components/common/empty-state'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; Loading @@ -15,18 +19,26 @@ export function ApplicationsPage() { const api = useOutletContext<Api>(); const { pathname } = useLocation(); const navigate = useNavigate(); const activePath = normalizePathname(pathname); const tabsRef = useRef<HTMLDivElement>(null); useEffect(() => { tabsRef.current ?.querySelector('[aria-selected="true"]') ?.scrollIntoView({ block: 'nearest', inline: 'nearest' }); }, [activePath]); if (`${ROUTES.APIS}/${api.id}` !== ROUTES.EDGE_APPLICATION_MANAGEMENT) { return <EmptyState title="Explore is not available for this API" />; } if (pathname === ROUTES.APPLICATIONS) { if (activePath === ROUTES.APPLICATIONS) { return <Navigate to={ROUTES.APPLICATION_PROFILES} replace />; } return ( <Tabs value={pathname} onValueChange={navigate} className="gap-4 py-4"> <Tabs value={activePath} onValueChange={navigate} className="gap-4 py-4"> <div className="overflow-x-auto px-4 lg:px-6"> <TabsList aria-label="Edge application views"> <TabsList ref={tabsRef} aria-label="Edge application views"> {applicationNavItems.map((item) => ( <TabsTrigger key={item.to} value={item.to}> <item.icon aria-hidden /> Loading Loading
portal-gui/src/app/config/navigation.ts +7 −1 Changes for portal-gui/src/app/config/navigation.ts: 7 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -114,6 +114,7 @@ export function canSeeNavItem( } export function isActivePath(pathname: string, to: string) { pathname = normalizePathname(pathname); if (to === '/') { return pathname === '/'; } Loading @@ -122,10 +123,14 @@ export function isActivePath(pathname: string, to: string) { } export function getActiveNavItem(pathname: string): NavItem | undefined { const navPath = findNavPath(mainNavItems, pathname); const navPath = findNavPath(mainNavItems, normalizePathname(pathname)); return navPath?.at(-1); } export function normalizePathname(pathname: string) { return pathname.replace(/\/+$/, '') || '/'; } function findNavPath(items: NavItem[], pathname: string): NavItem[] | null { for (const item of items) { if (item.to === pathname) { Loading @@ -145,6 +150,7 @@ export function getBreadcrumbItems( pathname: string, api?: Pick<Api, 'id' | 'title'> | null, ): BreadcrumbItem[] { pathname = normalizePathname(pathname); // Detail pages such as /apis/:id are not in the nav, so walk up to the // nearest page that is. let navPath: NavItem[] | null = null; Loading
portal-gui/src/components/common/app-breadcrumbs.tsx +3 −2 Changes for portal-gui/src/components/common/app-breadcrumbs.tsx: 3 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -9,11 +9,12 @@ import { BreadcrumbSeparator, } from '@/components/ui/breadcrumb'; import { getBreadcrumbItems } from '@/app/config/navigation'; import { getBreadcrumbItems, normalizePathname } from '@/app/config/navigation'; import type { Api } from '@/types/api'; export function AppBreadcrumbs({ api }: { api?: Api | null }) { const { pathname } = useLocation(); const activePath = normalizePathname(pathname); const items = getBreadcrumbItems(pathname, api); return ( Loading @@ -21,7 +22,7 @@ export function AppBreadcrumbs({ api }: { api?: Api | null }) { <BreadcrumbList> {items.map((item, index) => { const isLast = index === items.length - 1; const isCurrent = item.to === pathname; const isCurrent = item.to === activePath; return ( <Fragment key={item.to}> Loading
portal-gui/src/components/data-table/data-table.tsx +7 −0 Changes for portal-gui/src/components/data-table/data-table.tsx: 7 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -106,6 +106,13 @@ export function DataTable<TData>({ getSortedRowModel: getSortedRowModel(), }); const rows = table.getRowModel().rows; const lastPageIndex = Math.max(0, table.getPageCount() - 1); useEffect(() => { if (pagination.pageIndex > lastPageIndex) { table.setPageIndex(lastPageIndex); } }, [lastPageIndex, pagination.pageIndex, table]); const visibleIds = rows.map((row) => row.id).join(','); useEffect(() => { Loading
portal-gui/src/features/apis/api-workspace.tsx +8 −4 Changes for portal-gui/src/features/apis/api-workspace.tsx: 8 added lines, 4 removed lines. Original line number Diff line number Diff line Loading @@ -7,6 +7,7 @@ import { useParams, } from 'react-router-dom'; import { ROUTES } from '@/app/config/route'; import { normalizePathname } from '@/app/config/navigation'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { PageState } from '@/components/common/page-state'; Loading @@ -16,6 +17,7 @@ export function ApiWorkspacePage() { const { id } = useParams<{ id: string }>(); const { api, loading, error } = useOutletContext<ApiState>(); const { pathname } = useLocation(); const activePath = normalizePathname(pathname); const basePath = `${ROUTES.APIS}/${id}`; const hasExplore = basePath === ROUTES.EDGE_APPLICATION_MANAGEMENT; Loading Loading @@ -51,12 +53,14 @@ export function ApiWorkspacePage() { <nav aria-label="API sections" className="flex gap-2"> <Button asChild variant={pathname.endsWith('/overview') ? 'secondary' : 'ghost'} variant={ activePath.endsWith('/overview') ? 'secondary' : 'ghost' } > <Link to={`${basePath}/overview`} aria-current={ pathname.endsWith('/overview') ? 'page' : undefined activePath.endsWith('/overview') ? 'page' : undefined } > <BookOpen aria-hidden /> Loading @@ -67,7 +71,7 @@ export function ApiWorkspacePage() { <Button asChild variant={ pathname.startsWith(`${basePath}/explore`) activePath.startsWith(`${basePath}/explore`) ? 'secondary' : 'ghost' } Loading @@ -75,7 +79,7 @@ export function ApiWorkspacePage() { <Link to={`${basePath}/explore`} aria-current={ pathname.startsWith(`${basePath}/explore`) activePath.startsWith(`${basePath}/explore`) ? 'page' : undefined } Loading
portal-gui/src/features/applications/applications.tsx +16 −4 Changes for portal-gui/src/features/applications/applications.tsx: 16 added lines, 4 removed lines. Original line number Diff line number Diff line import { useEffect, useRef } from 'react'; import { Navigate, Outlet, Loading @@ -5,7 +6,10 @@ import { useNavigate, useOutletContext, } from 'react-router-dom'; import { applicationNavItems } from '@/app/config/navigation'; import { applicationNavItems, normalizePathname, } from '@/app/config/navigation'; import { ROUTES } from '@/app/config/route'; import { EmptyState } from '@/components/common/empty-state'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; Loading @@ -15,18 +19,26 @@ export function ApplicationsPage() { const api = useOutletContext<Api>(); const { pathname } = useLocation(); const navigate = useNavigate(); const activePath = normalizePathname(pathname); const tabsRef = useRef<HTMLDivElement>(null); useEffect(() => { tabsRef.current ?.querySelector('[aria-selected="true"]') ?.scrollIntoView({ block: 'nearest', inline: 'nearest' }); }, [activePath]); if (`${ROUTES.APIS}/${api.id}` !== ROUTES.EDGE_APPLICATION_MANAGEMENT) { return <EmptyState title="Explore is not available for this API" />; } if (pathname === ROUTES.APPLICATIONS) { if (activePath === ROUTES.APPLICATIONS) { return <Navigate to={ROUTES.APPLICATION_PROFILES} replace />; } return ( <Tabs value={pathname} onValueChange={navigate} className="gap-4 py-4"> <Tabs value={activePath} onValueChange={navigate} className="gap-4 py-4"> <div className="overflow-x-auto px-4 lg:px-6"> <TabsList aria-label="Edge application views"> <TabsList ref={tabsRef} aria-label="Edge application views"> {applicationNavItems.map((item) => ( <TabsTrigger key={item.to} value={item.to}> <item.icon aria-hidden /> Loading