Add risk assessments tab to risk list page
Surface the organization risk assessments list alongside the risk register by adding a shared Risks/Risk assessments tab bar to both list pages. Signed-off-by: Sacha Al Himdani <sacha@probo.com>
This commit is contained in:
@@ -1,37 +0,0 @@
|
|||||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
|
||||||
//
|
|
||||||
// Permission to use, copy, modify, and/or distribute this software for any
|
|
||||||
// purpose with or without fee is hereby granted, provided that the above
|
|
||||||
// copyright notice and this permission notice appear in all copies.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
||||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
||||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
||||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
||||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
||||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
||||||
// PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
|
|
||||||
import { lazy } from "@probo/react-lazy";
|
|
||||||
import type { AppRoute } from "@probo/routes";
|
|
||||||
|
|
||||||
import { PageSkeleton } from "#/components/skeletons/PageSkeleton";
|
|
||||||
|
|
||||||
export const riskAssessmentRoutes = [
|
|
||||||
{
|
|
||||||
path: "risk-assessments",
|
|
||||||
Fallback: PageSkeleton,
|
|
||||||
Component: lazy(
|
|
||||||
() =>
|
|
||||||
import("./RiskAssessmentsPageLoader"),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "risk-assessments/:riskAssessmentId",
|
|
||||||
Fallback: PageSkeleton,
|
|
||||||
Component: lazy(
|
|
||||||
() =>
|
|
||||||
import("./RiskAssessmentDetailPageLoader"),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
] satisfies AppRoute[];
|
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
import { Tabs } from "@probo/ui";
|
||||||
|
import { Suspense, useEffect } from "react";
|
||||||
|
import { useQueryLoader } from "react-relay";
|
||||||
|
import { Outlet } from "react-router";
|
||||||
|
|
||||||
|
import type { RisksTabsQuery } from "#/__generated__/core/RisksTabsQuery.graphql";
|
||||||
|
import { useOrganizationId } from "#/hooks/useOrganizationId";
|
||||||
|
|
||||||
|
import { RisksTabs, risksTabsQuery } from "./RisksTabs";
|
||||||
|
|
||||||
|
export default function RisksLayoutLoader() {
|
||||||
|
const organizationId = useOrganizationId();
|
||||||
|
const [queryRef, loadQuery]
|
||||||
|
= useQueryLoader<RisksTabsQuery>(risksTabsQuery);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadQuery({ organizationId });
|
||||||
|
}, [loadQuery, organizationId]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<Suspense fallback={<Tabs />}>
|
||||||
|
{queryRef && <RisksTabs queryRef={queryRef} />}
|
||||||
|
</Suspense>
|
||||||
|
|
||||||
|
<Outlet />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
61
apps/console/src/pages/organizations/risks/RisksTabs.tsx
Normal file
61
apps/console/src/pages/organizations/risks/RisksTabs.tsx
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
import { useTranslate } from "@probo/i18n";
|
||||||
|
import { TabLink, Tabs } from "@probo/ui";
|
||||||
|
import { graphql, type PreloadedQuery, usePreloadedQuery } from "react-relay";
|
||||||
|
|
||||||
|
import type { RisksTabsQuery } from "#/__generated__/core/RisksTabsQuery.graphql";
|
||||||
|
import { useOrganizationId } from "#/hooks/useOrganizationId";
|
||||||
|
|
||||||
|
export const risksTabsQuery = graphql`
|
||||||
|
query RisksTabsQuery($organizationId: ID!) {
|
||||||
|
organization: node(id: $organizationId) {
|
||||||
|
... on Organization {
|
||||||
|
canListRisks: permission(action: "core:risk:list")
|
||||||
|
canListRiskAssessments: permission(action: "core:risk-assessment:list")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
interface RisksTabsProps {
|
||||||
|
queryRef: PreloadedQuery<RisksTabsQuery>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function RisksTabs({ queryRef }: RisksTabsProps) {
|
||||||
|
const { __ } = useTranslate();
|
||||||
|
const organizationId = useOrganizationId();
|
||||||
|
const data = usePreloadedQuery<RisksTabsQuery>(risksTabsQuery, queryRef);
|
||||||
|
|
||||||
|
const canListRisks = data.organization?.canListRisks ?? false;
|
||||||
|
const canListRiskAssessments
|
||||||
|
= data.organization?.canListRiskAssessments ?? false;
|
||||||
|
const baseUrl = `/organizations/${organizationId}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tabs>
|
||||||
|
{canListRisks && (
|
||||||
|
<TabLink to={`${baseUrl}/risks`} end>
|
||||||
|
{__("Risks")}
|
||||||
|
</TabLink>
|
||||||
|
)}
|
||||||
|
{canListRiskAssessments && (
|
||||||
|
<TabLink to={`${baseUrl}/risk-assessments`} end>
|
||||||
|
{__("Risk assessments")}
|
||||||
|
</TabLink>
|
||||||
|
)}
|
||||||
|
</Tabs>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -22,11 +22,25 @@ import { PageSkeleton } from "#/components/skeletons/PageSkeleton";
|
|||||||
import { RisksPageSkeleton } from "#/components/skeletons/RisksPageSkeleton";
|
import { RisksPageSkeleton } from "#/components/skeletons/RisksPageSkeleton";
|
||||||
|
|
||||||
export const riskRoutes = [
|
export const riskRoutes = [
|
||||||
|
{
|
||||||
|
Fallback: PageSkeleton,
|
||||||
|
Component: lazy(() => import("./RisksLayoutLoader")),
|
||||||
|
children: [
|
||||||
{
|
{
|
||||||
path: "risks",
|
path: "risks",
|
||||||
Fallback: RisksPageSkeleton,
|
Fallback: RisksPageSkeleton,
|
||||||
Component: lazy(() => import("./RisksPageLoader")),
|
Component: lazy(() => import("./RisksPageLoader")),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "risk-assessments",
|
||||||
|
Fallback: PageSkeleton,
|
||||||
|
Component: lazy(
|
||||||
|
() =>
|
||||||
|
import("./risk-assessments/RiskAssessmentsPageLoader"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "risks/:riskId",
|
path: "risks/:riskId",
|
||||||
Fallback: PageSkeleton,
|
Fallback: PageSkeleton,
|
||||||
@@ -72,4 +86,12 @@ export const riskRoutes = [
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "risk-assessments/:riskAssessmentId",
|
||||||
|
Fallback: PageSkeleton,
|
||||||
|
Component: lazy(
|
||||||
|
() =>
|
||||||
|
import("./risk-assessments/RiskAssessmentDetailPageLoader"),
|
||||||
|
),
|
||||||
|
},
|
||||||
] satisfies AppRoute[];
|
] satisfies AppRoute[];
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ import { ViewerLayoutLoading } from "./pages/iam/memberships/ViewerLayoutLoading
|
|||||||
import { peopleRoutes } from "./pages/iam/organizations/people/routes";
|
import { peopleRoutes } from "./pages/iam/organizations/people/routes";
|
||||||
import { compliancePageRoutes } from "./pages/organizations/compliance-page/routes";
|
import { compliancePageRoutes } from "./pages/organizations/compliance-page/routes";
|
||||||
import { cookieBannerRoutes } from "./pages/organizations/cookie-banners/routes";
|
import { cookieBannerRoutes } from "./pages/organizations/cookie-banners/routes";
|
||||||
import { riskAssessmentRoutes } from "./pages/organizations/risk-assessments/routes";
|
|
||||||
import { riskRoutes } from "./pages/organizations/risks/routes";
|
import { riskRoutes } from "./pages/organizations/risks/routes";
|
||||||
import { thirdPartyRoutes } from "./pages/organizations/third-parties/routes";
|
import { thirdPartyRoutes } from "./pages/organizations/third-parties/routes";
|
||||||
import { CurrentUser } from "./providers/CurrentUser";
|
import { CurrentUser } from "./providers/CurrentUser";
|
||||||
@@ -302,7 +301,6 @@ const routes = [
|
|||||||
},
|
},
|
||||||
...peopleRoutes,
|
...peopleRoutes,
|
||||||
...riskRoutes,
|
...riskRoutes,
|
||||||
...riskAssessmentRoutes,
|
|
||||||
...measureRoutes,
|
...measureRoutes,
|
||||||
...documentsRoutes,
|
...documentsRoutes,
|
||||||
...thirdPartyRoutes,
|
...thirdPartyRoutes,
|
||||||
|
|||||||
Reference in New Issue
Block a user