- {getFrameworkName(frameworkInstance.frameworkId)}
+ {getFrameworkDetails(frameworkInstance.frameworkId).name}
-
-
-
+
+
);
}
diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/layout.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/layout.tsx
index b8e126b65b..5287c8fc59 100644
--- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/layout.tsx
+++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/layout.tsx
@@ -1,8 +1,7 @@
-import { auth } from "@bubba/auth";
import { getI18n } from "@/locales/server";
-import { redirect } from "next/navigation";
-import { SecondaryMenu } from "@bubba/ui/secondary-menu";
+import { auth } from "@bubba/auth";
import { headers } from "next/headers";
+import { redirect } from "next/navigation";
export default async function Layout({
children,
@@ -21,15 +20,6 @@ export default async function Layout({
return (
-
-
{children}
);
diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/lib/getFrameworkDetails.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/lib/getFrameworkDetails.ts
new file mode 100644
index 0000000000..4759675219
--- /dev/null
+++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/lib/getFrameworkDetails.ts
@@ -0,0 +1,6 @@
+import { frameworks } from "@bubba/data";
+import { FrameworkId } from "@bubba/db/types";
+
+export function getFrameworkDetails(frameworkId: FrameworkId) {
+ return frameworks[frameworkId as keyof typeof frameworks];
+}
diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/lib/getFrameworkName.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/lib/getFrameworkName.ts
deleted file mode 100644
index dbe01fc448..0000000000
--- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/lib/getFrameworkName.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { frameworks } from "@bubba/data";
-import { FrameworkId } from "@bubba/db/types";
-
-export function getFrameworkName(frameworkId: FrameworkId) {
- return (
- frameworks[frameworkId as keyof typeof frameworks]?.name || frameworkId
- );
-}
diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/lib/getRequirementDetails.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/lib/getRequirementDetails.ts
new file mode 100644
index 0000000000..983dc8454c
--- /dev/null
+++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/lib/getRequirementDetails.ts
@@ -0,0 +1,14 @@
+import { requirements } from "@bubba/data";
+
+export function getRequirementDetails(
+ frameworkId: keyof typeof requirements,
+ requirementId: string,
+) {
+ const frameworkRequirements =
+ requirements[frameworkId as keyof typeof requirements] || [];
+
+ const requirement =
+ frameworkRequirements[requirementId as keyof typeof frameworkRequirements];
+
+ return requirement;
+}
diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/page.tsx
index ccdd75926e..d0aa1c3ea4 100644
--- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/page.tsx
+++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/page.tsx
@@ -4,6 +4,7 @@ import { headers } from "next/headers";
import { redirect } from "next/navigation";
import { FrameworksOverview } from "./components/FrameworksOverview";
import { getAllFrameworkInstancesWithControls } from "./data/getAllFrameworkInstancesWithControls";
+import PageWithBreadcrumb from "../../../../../../components/pages/PageWithBreadcrumb";
export async function generateMetadata() {
const t = await getI18n();
@@ -30,9 +31,8 @@ export default async function DashboardPage() {
});
return (
-
+
+
+
);
}
diff --git a/apps/app/src/components/pages/PageWithBreadcrumb.tsx b/apps/app/src/components/pages/PageWithBreadcrumb.tsx
new file mode 100644
index 0000000000..43f0b6cf42
--- /dev/null
+++ b/apps/app/src/components/pages/PageWithBreadcrumb.tsx
@@ -0,0 +1,124 @@
+import {
+ Breadcrumb,
+ BreadcrumbItem,
+ BreadcrumbLink,
+ BreadcrumbList,
+ BreadcrumbPage,
+ BreadcrumbSeparator,
+ BreadcrumbEllipsis,
+} from "@bubba/ui/breadcrumb";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from "@bubba/ui/dropdown-menu";
+import { ChevronDown } from "lucide-react";
+import Link from "next/link";
+import React from "react";
+
+interface BreadcrumbDropdownItem {
+ label: string;
+ href: string;
+}
+
+interface BreadcrumbItemType {
+ label: string;
+ href?: string;
+ dropdown?: BreadcrumbDropdownItem[];
+ current?: boolean;
+}
+
+interface PageLayoutProps {
+ children: React.ReactNode;
+ breadcrumbs: BreadcrumbItemType[];
+ /**
+ * Maximum number of visible items before collapsing
+ * @default 3
+ */
+ maxItems?: number;
+}
+
+export default function PageWithBreadcrumb({
+ children,
+ breadcrumbs,
+ maxItems = 3,
+}: PageLayoutProps) {
+ const totalItems = breadcrumbs.length;
+ const shouldCollapse = totalItems > maxItems;
+
+ const visibleItems = shouldCollapse
+ ? [breadcrumbs[0], ...breadcrumbs.slice(totalItems - (maxItems - 1))]
+ : breadcrumbs;
+
+ const hiddenItems = shouldCollapse
+ ? breadcrumbs.slice(1, totalItems - (maxItems - 1))
+ : [];
+
+ return (
+
+
+
+ {visibleItems.map((item, index) => {
+ const isFirst = index === 0;
+ const isLast = index === visibleItems.length - 1;
+ const showEllipsis = shouldCollapse && index === 1;
+
+ return (
+
+
+ {item.dropdown ? (
+
+
+ {item.label}
+
+
+
+ {item.dropdown.map((dropdownItem) => (
+
+
+ {dropdownItem.label}
+
+
+ ))}
+
+
+ ) : item.current ? (
+ {item.label}
+ ) : (
+
+ {item.label}
+
+ )}
+
+ {!isLast && }
+ {showEllipsis && hiddenItems.length > 0 && (
+ <>
+
+
+
+
+
+
+ {hiddenItems.map((hiddenItem) => (
+
+
+ {hiddenItem.label}
+
+
+ ))}
+
+
+
+
+ >
+ )}
+
+ );
+ })}
+
+
+ {children}
+
+ );
+}