@@ -123,7 +123,7 @@ function App() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/frameworks/:frameworkId/:controlId"
|
path="/frameworks/:frameworkId/controls/:controlId"
|
||||||
element={
|
element={
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<ErrorBoundaryWithLocation>
|
<ErrorBoundaryWithLocation>
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
import React, { createContext, useContext, useState, useCallback } from "react";
|
|
||||||
|
|
||||||
type BreadcrumbContextType = {
|
|
||||||
segments: Record<string, string>;
|
|
||||||
setBreadcrumbSegment: (path: string, display: string) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
const BreadcrumbContext = createContext<BreadcrumbContextType | undefined>(
|
|
||||||
undefined,
|
|
||||||
);
|
|
||||||
|
|
||||||
export function BreadcrumbProvider({
|
|
||||||
children,
|
|
||||||
}: {
|
|
||||||
children: React.ReactNode;
|
|
||||||
}) {
|
|
||||||
const [segments, setSegments] = useState<Record<string, string>>({});
|
|
||||||
|
|
||||||
const setBreadcrumbSegment = useCallback((path: string, display: string) => {
|
|
||||||
setSegments((prev) => ({
|
|
||||||
...prev,
|
|
||||||
[path]: display,
|
|
||||||
}));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<BreadcrumbContext.Provider value={{ segments, setBreadcrumbSegment }}>
|
|
||||||
{children}
|
|
||||||
</BreadcrumbContext.Provider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useBreadcrumb() {
|
|
||||||
const context = useContext(BreadcrumbContext);
|
|
||||||
if (context === undefined) {
|
|
||||||
throw new Error("useBreadcrumb must be used within a BreadcrumbProvider");
|
|
||||||
}
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,11 @@
|
|||||||
import { Link, Outlet, useLocation } from "react-router";
|
import {
|
||||||
|
Link,
|
||||||
|
Outlet,
|
||||||
|
Route,
|
||||||
|
Routes,
|
||||||
|
useLocation,
|
||||||
|
useParams,
|
||||||
|
} from "react-router";
|
||||||
import { AppSidebar } from "@/components/AppSidebar";
|
import { AppSidebar } from "@/components/AppSidebar";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {
|
import {
|
||||||
@@ -15,33 +22,15 @@ import {
|
|||||||
SidebarProvider,
|
SidebarProvider,
|
||||||
SidebarTrigger,
|
SidebarTrigger,
|
||||||
} from "@/components/ui/sidebar";
|
} from "@/components/ui/sidebar";
|
||||||
import {
|
|
||||||
BreadcrumbProvider,
|
|
||||||
useBreadcrumb,
|
|
||||||
} from "@/contexts/BreadcrumbContext";
|
|
||||||
import { Toaster } from "@/components/ui/toaster";
|
import { Toaster } from "@/components/ui/toaster";
|
||||||
|
import { graphql } from "react-relay";
|
||||||
|
import { useLazyLoadQuery } from "react-relay";
|
||||||
|
import { ConsoleLayoutBreadcrumbFrameworkOverviewQuery } from "./__generated__/ConsoleLayoutBreadcrumbFrameworkOverviewQuery.graphql";
|
||||||
|
import { ConsoleLayoutBreadcrumbPeopleOverviewQuery } from "./__generated__/ConsoleLayoutBreadcrumbPeopleOverviewQuery.graphql";
|
||||||
|
import { ConsoleLayoutBreadcrumbVendorOverviewQuery } from "./__generated__/ConsoleLayoutBreadcrumbVendorOverviewQuery.graphql";
|
||||||
|
import { ConsoleLayoutBreadcrumbControlOverviewQuery } from "./__generated__/ConsoleLayoutBreadcrumbControlOverviewQuery.graphql";
|
||||||
|
|
||||||
function BreadcrumbNavigation() {
|
function BreadcrumbHome() {
|
||||||
const location = useLocation();
|
|
||||||
const pathSegments = location.pathname.split("/").filter(Boolean);
|
|
||||||
const { segments } = useBreadcrumb();
|
|
||||||
|
|
||||||
const getBreadcrumbText = (segment: string, path: string) => {
|
|
||||||
// Check if we have a custom segment name
|
|
||||||
const fullPath = pathSegments
|
|
||||||
.slice(0, pathSegments.indexOf(segment) + 1)
|
|
||||||
.join("/");
|
|
||||||
|
|
||||||
// Look for patterns like 'vendors/:id' in our segments
|
|
||||||
const patternPath = fullPath.replace(/\/[^/]+$/, "/:id");
|
|
||||||
if (segments[patternPath]) {
|
|
||||||
return segments[patternPath];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default to capitalized segment
|
|
||||||
return segment.charAt(0).toUpperCase() + segment.slice(1);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Breadcrumb>
|
<Breadcrumb>
|
||||||
<BreadcrumbList>
|
<BreadcrumbList>
|
||||||
@@ -50,51 +39,238 @@ function BreadcrumbNavigation() {
|
|||||||
<Link to="/">Home</Link>
|
<Link to="/">Home</Link>
|
||||||
</BreadcrumbLink>
|
</BreadcrumbLink>
|
||||||
</BreadcrumbItem>
|
</BreadcrumbItem>
|
||||||
{pathSegments.map((segment, index) => {
|
<Outlet />
|
||||||
const path = `/${pathSegments.slice(0, index + 1).join("/")}`;
|
|
||||||
const isLast = index === pathSegments.length - 1;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<React.Fragment key={path}>
|
|
||||||
<BreadcrumbSeparator />
|
|
||||||
<BreadcrumbItem>
|
|
||||||
{isLast ? (
|
|
||||||
<BreadcrumbPage>
|
|
||||||
{getBreadcrumbText(segment, path)}
|
|
||||||
</BreadcrumbPage>
|
|
||||||
) : (
|
|
||||||
<BreadcrumbLink asChild>
|
|
||||||
<Link to={path}>{getBreadcrumbText(segment, path)}</Link>
|
|
||||||
</BreadcrumbLink>
|
|
||||||
)}
|
|
||||||
</BreadcrumbItem>
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</BreadcrumbList>
|
</BreadcrumbList>
|
||||||
</Breadcrumb>
|
</Breadcrumb>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ConsoleLayout() {
|
function BreadcrumbFrameworkList() {
|
||||||
return (
|
return (
|
||||||
<BreadcrumbProvider>
|
<>
|
||||||
<SidebarProvider>
|
<BreadcrumbSeparator />
|
||||||
<AppSidebar />
|
<BreadcrumbItem>
|
||||||
<SidebarInset>
|
<BreadcrumbLink asChild>
|
||||||
<header className="flex h-16 shrink-0 items-center gap-2">
|
<Link to="/frameworks">Frameworks</Link>
|
||||||
<div className="flex items-center gap-2 px-4">
|
</BreadcrumbLink>
|
||||||
<SidebarTrigger className="-ml-1" />
|
</BreadcrumbItem>
|
||||||
<Separator orientation="vertical" className="mr-2 h-4" />
|
<Outlet />
|
||||||
<BreadcrumbNavigation />
|
</>
|
||||||
</div>
|
);
|
||||||
</header>
|
}
|
||||||
<div className="flex flex-1 flex-col gap-4 p-4 pt-0">
|
|
||||||
<Outlet />
|
function BreadcrumbFrameworkOverview() {
|
||||||
<Toaster />
|
const { frameworkId } = useParams();
|
||||||
</div>
|
const data = useLazyLoadQuery<ConsoleLayoutBreadcrumbFrameworkOverviewQuery>(
|
||||||
</SidebarInset>
|
graphql`
|
||||||
</SidebarProvider>
|
query ConsoleLayoutBreadcrumbFrameworkOverviewQuery($frameworkId: ID!) {
|
||||||
</BreadcrumbProvider>
|
framework: node(id: $frameworkId) {
|
||||||
|
id
|
||||||
|
... on Framework {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
{ frameworkId: frameworkId! },
|
||||||
|
{ fetchPolicy: "store-or-network" },
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<BreadcrumbSeparator />
|
||||||
|
<BreadcrumbItem>
|
||||||
|
<BreadcrumbLink asChild>
|
||||||
|
<Link to={`/frameworks/${data.framework.id}`}>
|
||||||
|
{data.framework.name}
|
||||||
|
</Link>
|
||||||
|
</BreadcrumbLink>
|
||||||
|
</BreadcrumbItem>
|
||||||
|
<Outlet />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function BreadcrumbPeopleList() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<BreadcrumbSeparator />
|
||||||
|
<BreadcrumbItem>
|
||||||
|
<BreadcrumbLink asChild>
|
||||||
|
<Link to="/peoples">People</Link>
|
||||||
|
</BreadcrumbLink>
|
||||||
|
</BreadcrumbItem>
|
||||||
|
<Outlet />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function BreadcrumbPeopleOverview() {
|
||||||
|
const { peopleId } = useParams();
|
||||||
|
const data = useLazyLoadQuery<ConsoleLayoutBreadcrumbPeopleOverviewQuery>(
|
||||||
|
graphql`
|
||||||
|
query ConsoleLayoutBreadcrumbPeopleOverviewQuery($peopleId: ID!) {
|
||||||
|
people: node(id: $peopleId) {
|
||||||
|
id
|
||||||
|
... on People {
|
||||||
|
fullName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
{ peopleId: peopleId! },
|
||||||
|
{ fetchPolicy: "store-or-network" },
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<BreadcrumbSeparator />
|
||||||
|
<BreadcrumbItem>
|
||||||
|
<BreadcrumbLink asChild>
|
||||||
|
<Link to={`/peoples/${data.people.id}`}>{data.people.fullName}</Link>
|
||||||
|
</BreadcrumbLink>
|
||||||
|
</BreadcrumbItem>
|
||||||
|
<Outlet />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function BreadcrumbVendorList() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<BreadcrumbSeparator />
|
||||||
|
<BreadcrumbItem>
|
||||||
|
<BreadcrumbLink asChild>
|
||||||
|
<Link to="/vendors">Vendors</Link>
|
||||||
|
</BreadcrumbLink>
|
||||||
|
</BreadcrumbItem>
|
||||||
|
<Outlet />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function BreadcrumbVendorOverview() {
|
||||||
|
const { vendorId } = useParams();
|
||||||
|
const data = useLazyLoadQuery<ConsoleLayoutBreadcrumbVendorOverviewQuery>(
|
||||||
|
graphql`
|
||||||
|
query ConsoleLayoutBreadcrumbVendorOverviewQuery($vendorId: ID!) {
|
||||||
|
vendor: node(id: $vendorId) {
|
||||||
|
id
|
||||||
|
... on Vendor {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
{ vendorId: vendorId! },
|
||||||
|
{ fetchPolicy: "store-or-network" },
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<BreadcrumbSeparator />
|
||||||
|
<BreadcrumbItem>
|
||||||
|
<BreadcrumbLink asChild>
|
||||||
|
<Link to={`/vendors/${data.vendor.id}`}>{data.vendor.name}</Link>
|
||||||
|
</BreadcrumbLink>
|
||||||
|
</BreadcrumbItem>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function BreadcrumbControlOverview() {
|
||||||
|
const { frameworkId, controlId } = useParams();
|
||||||
|
const data = useLazyLoadQuery<ConsoleLayoutBreadcrumbControlOverviewQuery>(
|
||||||
|
graphql`
|
||||||
|
query ConsoleLayoutBreadcrumbControlOverviewQuery(
|
||||||
|
$frameworkId: ID!
|
||||||
|
$controlId: ID!
|
||||||
|
) {
|
||||||
|
framework: node(id: $frameworkId) {
|
||||||
|
id
|
||||||
|
... on Framework {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
control: node(id: $controlId) {
|
||||||
|
id
|
||||||
|
... on Control {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
{ frameworkId: frameworkId!, controlId: controlId! },
|
||||||
|
{ fetchPolicy: "store-or-network" },
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<BreadcrumbSeparator />
|
||||||
|
<BreadcrumbItem>
|
||||||
|
<BreadcrumbLink asChild>
|
||||||
|
<Link to={`/frameworks/${data.framework.id}`}>
|
||||||
|
{data.framework.name}
|
||||||
|
</Link>
|
||||||
|
</BreadcrumbLink>
|
||||||
|
</BreadcrumbItem>
|
||||||
|
<BreadcrumbSeparator />
|
||||||
|
<BreadcrumbItem>
|
||||||
|
<BreadcrumbLink asChild>
|
||||||
|
<Link
|
||||||
|
to={`/frameworks/${data.framework.id}/controls/${data.control.id}`}
|
||||||
|
>
|
||||||
|
{data.control.name}
|
||||||
|
</Link>
|
||||||
|
</BreadcrumbLink>
|
||||||
|
</BreadcrumbItem>
|
||||||
|
<Outlet />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ConsoleLayout() {
|
||||||
|
return (
|
||||||
|
<SidebarProvider>
|
||||||
|
<AppSidebar />
|
||||||
|
<SidebarInset>
|
||||||
|
<header className="flex h-16 shrink-0 items-center gap-2">
|
||||||
|
<div className="flex items-center gap-2 px-4">
|
||||||
|
<SidebarTrigger className="-ml-1" />
|
||||||
|
<Separator orientation="vertical" className="mr-2 h-4" />
|
||||||
|
<Routes>
|
||||||
|
<Route path="/" element={<BreadcrumbHome />}>
|
||||||
|
<Route path="/frameworks" element={<BreadcrumbFrameworkList />}>
|
||||||
|
<Route
|
||||||
|
path="/frameworks/:frameworkId"
|
||||||
|
element={<BreadcrumbFrameworkOverview />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/frameworks/:frameworkId/controls/:controlId"
|
||||||
|
element={<BreadcrumbControlOverview />}
|
||||||
|
/>
|
||||||
|
</Route>
|
||||||
|
<Route path="/peoples" element={<BreadcrumbPeopleList />}>
|
||||||
|
<Route
|
||||||
|
path="/peoples/:peopleId"
|
||||||
|
element={<BreadcrumbPeopleOverview />}
|
||||||
|
/>
|
||||||
|
</Route>
|
||||||
|
<Route path="/vendors" element={<BreadcrumbVendorList />}>
|
||||||
|
<Route
|
||||||
|
path="/vendors/:vendorId"
|
||||||
|
element={<BreadcrumbVendorOverview />}
|
||||||
|
/>
|
||||||
|
</Route>
|
||||||
|
</Route>
|
||||||
|
</Routes>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<div className="flex flex-1 flex-col gap-4 p-4 pt-0">
|
||||||
|
<Outlet />
|
||||||
|
<Toaster />
|
||||||
|
</div>
|
||||||
|
</SidebarInset>
|
||||||
|
</SidebarProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
127
apps/console/src/layouts/__generated__/ConsoleLayoutBreadcrumbControlOverviewQuery.graphql.ts
generated
Normal file
127
apps/console/src/layouts/__generated__/ConsoleLayoutBreadcrumbControlOverviewQuery.graphql.ts
generated
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
/**
|
||||||
|
* @generated SignedSource<<2369d48ac2f92f14d77f24d397fe7c18>>
|
||||||
|
* @lightSyntaxTransform
|
||||||
|
* @nogrep
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
import { ConcreteRequest } from 'relay-runtime';
|
||||||
|
export type ConsoleLayoutBreadcrumbControlOverviewQuery$variables = {
|
||||||
|
controlId: string;
|
||||||
|
};
|
||||||
|
export type ConsoleLayoutBreadcrumbControlOverviewQuery$data = {
|
||||||
|
readonly vendor: {
|
||||||
|
readonly id: string;
|
||||||
|
readonly name?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export type ConsoleLayoutBreadcrumbControlOverviewQuery = {
|
||||||
|
response: ConsoleLayoutBreadcrumbControlOverviewQuery$data;
|
||||||
|
variables: ConsoleLayoutBreadcrumbControlOverviewQuery$variables;
|
||||||
|
};
|
||||||
|
|
||||||
|
const node: ConcreteRequest = (function(){
|
||||||
|
var v0 = [
|
||||||
|
{
|
||||||
|
"defaultValue": null,
|
||||||
|
"kind": "LocalArgument",
|
||||||
|
"name": "controlId"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
v1 = [
|
||||||
|
{
|
||||||
|
"kind": "Variable",
|
||||||
|
"name": "id",
|
||||||
|
"variableName": "controlId"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
v2 = {
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "id",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
v3 = {
|
||||||
|
"kind": "InlineFragment",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "name",
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "Control",
|
||||||
|
"abstractKey": null
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
"fragment": {
|
||||||
|
"argumentDefinitions": (v0/*: any*/),
|
||||||
|
"kind": "Fragment",
|
||||||
|
"metadata": null,
|
||||||
|
"name": "ConsoleLayoutBreadcrumbControlOverviewQuery",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": "vendor",
|
||||||
|
"args": (v1/*: any*/),
|
||||||
|
"concreteType": null,
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "node",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
(v2/*: any*/),
|
||||||
|
(v3/*: any*/)
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "Query",
|
||||||
|
"abstractKey": null
|
||||||
|
},
|
||||||
|
"kind": "Request",
|
||||||
|
"operation": {
|
||||||
|
"argumentDefinitions": (v0/*: any*/),
|
||||||
|
"kind": "Operation",
|
||||||
|
"name": "ConsoleLayoutBreadcrumbControlOverviewQuery",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": "vendor",
|
||||||
|
"args": (v1/*: any*/),
|
||||||
|
"concreteType": null,
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "node",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "__typename",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
(v2/*: any*/),
|
||||||
|
(v3/*: any*/)
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"cacheID": "00384256b541f268704b25cbb9c2cb0a",
|
||||||
|
"id": null,
|
||||||
|
"metadata": {},
|
||||||
|
"name": "ConsoleLayoutBreadcrumbControlOverviewQuery",
|
||||||
|
"operationKind": "query",
|
||||||
|
"text": "query ConsoleLayoutBreadcrumbControlOverviewQuery(\n $controlId: ID!\n) {\n vendor: node(id: $controlId) {\n __typename\n id\n ... on Control {\n name\n }\n }\n}\n"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
(node as any).hash = "cfa3ce6c9e7a6c25f7b147ba61a00b55";
|
||||||
|
|
||||||
|
export default node;
|
||||||
127
apps/console/src/layouts/__generated__/ConsoleLayoutBreadcrumbFrameworkOverviewQuery.graphql.ts
generated
Normal file
127
apps/console/src/layouts/__generated__/ConsoleLayoutBreadcrumbFrameworkOverviewQuery.graphql.ts
generated
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
/**
|
||||||
|
* @generated SignedSource<<b762e82a823e00ae26cd82eff55352b7>>
|
||||||
|
* @lightSyntaxTransform
|
||||||
|
* @nogrep
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
import { ConcreteRequest } from 'relay-runtime';
|
||||||
|
export type ConsoleLayoutBreadcrumbFrameworkOverviewQuery$variables = {
|
||||||
|
peopleId: string;
|
||||||
|
};
|
||||||
|
export type ConsoleLayoutBreadcrumbFrameworkOverviewQuery$data = {
|
||||||
|
readonly people: {
|
||||||
|
readonly fullName?: string;
|
||||||
|
readonly id: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export type ConsoleLayoutBreadcrumbFrameworkOverviewQuery = {
|
||||||
|
response: ConsoleLayoutBreadcrumbFrameworkOverviewQuery$data;
|
||||||
|
variables: ConsoleLayoutBreadcrumbFrameworkOverviewQuery$variables;
|
||||||
|
};
|
||||||
|
|
||||||
|
const node: ConcreteRequest = (function(){
|
||||||
|
var v0 = [
|
||||||
|
{
|
||||||
|
"defaultValue": null,
|
||||||
|
"kind": "LocalArgument",
|
||||||
|
"name": "peopleId"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
v1 = [
|
||||||
|
{
|
||||||
|
"kind": "Variable",
|
||||||
|
"name": "id",
|
||||||
|
"variableName": "peopleId"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
v2 = {
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "id",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
v3 = {
|
||||||
|
"kind": "InlineFragment",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "fullName",
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "People",
|
||||||
|
"abstractKey": null
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
"fragment": {
|
||||||
|
"argumentDefinitions": (v0/*: any*/),
|
||||||
|
"kind": "Fragment",
|
||||||
|
"metadata": null,
|
||||||
|
"name": "ConsoleLayoutBreadcrumbFrameworkOverviewQuery",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": "people",
|
||||||
|
"args": (v1/*: any*/),
|
||||||
|
"concreteType": null,
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "node",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
(v2/*: any*/),
|
||||||
|
(v3/*: any*/)
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "Query",
|
||||||
|
"abstractKey": null
|
||||||
|
},
|
||||||
|
"kind": "Request",
|
||||||
|
"operation": {
|
||||||
|
"argumentDefinitions": (v0/*: any*/),
|
||||||
|
"kind": "Operation",
|
||||||
|
"name": "ConsoleLayoutBreadcrumbFrameworkOverviewQuery",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": "people",
|
||||||
|
"args": (v1/*: any*/),
|
||||||
|
"concreteType": null,
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "node",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "__typename",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
(v2/*: any*/),
|
||||||
|
(v3/*: any*/)
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"cacheID": "38bda48229b0d45b23dff36b33fa3adb",
|
||||||
|
"id": null,
|
||||||
|
"metadata": {},
|
||||||
|
"name": "ConsoleLayoutBreadcrumbFrameworkOverviewQuery",
|
||||||
|
"operationKind": "query",
|
||||||
|
"text": "query ConsoleLayoutBreadcrumbFrameworkOverviewQuery(\n $peopleId: ID!\n) {\n people: node(id: $peopleId) {\n __typename\n id\n ... on People {\n fullName\n }\n }\n}\n"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
(node as any).hash = "1f4b25d943e1d8a79c3e77936833e998";
|
||||||
|
|
||||||
|
export default node;
|
||||||
127
apps/console/src/layouts/__generated__/ConsoleLayoutBreadcrumbPeopleOverviewQuery.graphql.ts
generated
Normal file
127
apps/console/src/layouts/__generated__/ConsoleLayoutBreadcrumbPeopleOverviewQuery.graphql.ts
generated
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
/**
|
||||||
|
* @generated SignedSource<<6f086aeafe55fd91abdf75b5832f76e8>>
|
||||||
|
* @lightSyntaxTransform
|
||||||
|
* @nogrep
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
import { ConcreteRequest } from 'relay-runtime';
|
||||||
|
export type ConsoleLayoutBreadcrumbPeopleOverviewQuery$variables = {
|
||||||
|
peopleId: string;
|
||||||
|
};
|
||||||
|
export type ConsoleLayoutBreadcrumbPeopleOverviewQuery$data = {
|
||||||
|
readonly people: {
|
||||||
|
readonly fullName?: string;
|
||||||
|
readonly id: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export type ConsoleLayoutBreadcrumbPeopleOverviewQuery = {
|
||||||
|
response: ConsoleLayoutBreadcrumbPeopleOverviewQuery$data;
|
||||||
|
variables: ConsoleLayoutBreadcrumbPeopleOverviewQuery$variables;
|
||||||
|
};
|
||||||
|
|
||||||
|
const node: ConcreteRequest = (function(){
|
||||||
|
var v0 = [
|
||||||
|
{
|
||||||
|
"defaultValue": null,
|
||||||
|
"kind": "LocalArgument",
|
||||||
|
"name": "peopleId"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
v1 = [
|
||||||
|
{
|
||||||
|
"kind": "Variable",
|
||||||
|
"name": "id",
|
||||||
|
"variableName": "peopleId"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
v2 = {
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "id",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
v3 = {
|
||||||
|
"kind": "InlineFragment",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "fullName",
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "People",
|
||||||
|
"abstractKey": null
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
"fragment": {
|
||||||
|
"argumentDefinitions": (v0/*: any*/),
|
||||||
|
"kind": "Fragment",
|
||||||
|
"metadata": null,
|
||||||
|
"name": "ConsoleLayoutBreadcrumbPeopleOverviewQuery",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": "people",
|
||||||
|
"args": (v1/*: any*/),
|
||||||
|
"concreteType": null,
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "node",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
(v2/*: any*/),
|
||||||
|
(v3/*: any*/)
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "Query",
|
||||||
|
"abstractKey": null
|
||||||
|
},
|
||||||
|
"kind": "Request",
|
||||||
|
"operation": {
|
||||||
|
"argumentDefinitions": (v0/*: any*/),
|
||||||
|
"kind": "Operation",
|
||||||
|
"name": "ConsoleLayoutBreadcrumbPeopleOverviewQuery",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": "people",
|
||||||
|
"args": (v1/*: any*/),
|
||||||
|
"concreteType": null,
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "node",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "__typename",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
(v2/*: any*/),
|
||||||
|
(v3/*: any*/)
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"cacheID": "ae4480910071c40c530fabf42d140afd",
|
||||||
|
"id": null,
|
||||||
|
"metadata": {},
|
||||||
|
"name": "ConsoleLayoutBreadcrumbPeopleOverviewQuery",
|
||||||
|
"operationKind": "query",
|
||||||
|
"text": "query ConsoleLayoutBreadcrumbPeopleOverviewQuery(\n $peopleId: ID!\n) {\n people: node(id: $peopleId) {\n __typename\n id\n ... on People {\n fullName\n }\n }\n}\n"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
(node as any).hash = "ffd8d5be8b9b8a930c956c8d292715d8";
|
||||||
|
|
||||||
|
export default node;
|
||||||
127
apps/console/src/layouts/__generated__/ConsoleLayoutBreadcrumbVendorOverviewQuery.graphql.ts
generated
Normal file
127
apps/console/src/layouts/__generated__/ConsoleLayoutBreadcrumbVendorOverviewQuery.graphql.ts
generated
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
/**
|
||||||
|
* @generated SignedSource<<266ca8730d2832be4d4c96d325c3e4ab>>
|
||||||
|
* @lightSyntaxTransform
|
||||||
|
* @nogrep
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
import { ConcreteRequest } from 'relay-runtime';
|
||||||
|
export type ConsoleLayoutBreadcrumbVendorOverviewQuery$variables = {
|
||||||
|
vendorId: string;
|
||||||
|
};
|
||||||
|
export type ConsoleLayoutBreadcrumbVendorOverviewQuery$data = {
|
||||||
|
readonly vendor: {
|
||||||
|
readonly id: string;
|
||||||
|
readonly name?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export type ConsoleLayoutBreadcrumbVendorOverviewQuery = {
|
||||||
|
response: ConsoleLayoutBreadcrumbVendorOverviewQuery$data;
|
||||||
|
variables: ConsoleLayoutBreadcrumbVendorOverviewQuery$variables;
|
||||||
|
};
|
||||||
|
|
||||||
|
const node: ConcreteRequest = (function(){
|
||||||
|
var v0 = [
|
||||||
|
{
|
||||||
|
"defaultValue": null,
|
||||||
|
"kind": "LocalArgument",
|
||||||
|
"name": "vendorId"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
v1 = [
|
||||||
|
{
|
||||||
|
"kind": "Variable",
|
||||||
|
"name": "id",
|
||||||
|
"variableName": "vendorId"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
v2 = {
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "id",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
v3 = {
|
||||||
|
"kind": "InlineFragment",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "name",
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "Vendor",
|
||||||
|
"abstractKey": null
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
"fragment": {
|
||||||
|
"argumentDefinitions": (v0/*: any*/),
|
||||||
|
"kind": "Fragment",
|
||||||
|
"metadata": null,
|
||||||
|
"name": "ConsoleLayoutBreadcrumbVendorOverviewQuery",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": "vendor",
|
||||||
|
"args": (v1/*: any*/),
|
||||||
|
"concreteType": null,
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "node",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
(v2/*: any*/),
|
||||||
|
(v3/*: any*/)
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "Query",
|
||||||
|
"abstractKey": null
|
||||||
|
},
|
||||||
|
"kind": "Request",
|
||||||
|
"operation": {
|
||||||
|
"argumentDefinitions": (v0/*: any*/),
|
||||||
|
"kind": "Operation",
|
||||||
|
"name": "ConsoleLayoutBreadcrumbVendorOverviewQuery",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": "vendor",
|
||||||
|
"args": (v1/*: any*/),
|
||||||
|
"concreteType": null,
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "node",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "__typename",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
(v2/*: any*/),
|
||||||
|
(v3/*: any*/)
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"cacheID": "d0b39b21eec610e1162705b7e2799468",
|
||||||
|
"id": null,
|
||||||
|
"metadata": {},
|
||||||
|
"name": "ConsoleLayoutBreadcrumbVendorOverviewQuery",
|
||||||
|
"operationKind": "query",
|
||||||
|
"text": "query ConsoleLayoutBreadcrumbVendorOverviewQuery(\n $vendorId: ID!\n) {\n vendor: node(id: $vendorId) {\n __typename\n id\n ... on Vendor {\n name\n }\n }\n}\n"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
(node as any).hash = "20bb1420f2d13cf7974591da5e6b654f";
|
||||||
|
|
||||||
|
export default node;
|
||||||
@@ -10,11 +10,18 @@ import { CheckCircle2 } from "lucide-react";
|
|||||||
import { Card, CardContent } from "@/components/ui/card";
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
|
|
||||||
import { Helmet } from "react-helmet-async";
|
import { Helmet } from "react-helmet-async";
|
||||||
import { useBreadcrumb } from "@/contexts/BreadcrumbContext";
|
|
||||||
import type { ControlOverviewPageQuery as ControlOverviewPageQueryType } from "./__generated__/ControlOverviewPageQuery.graphql";
|
import type { ControlOverviewPageQuery as ControlOverviewPageQueryType } from "./__generated__/ControlOverviewPageQuery.graphql";
|
||||||
|
|
||||||
const controlOverviewPageQuery = graphql`
|
const controlOverviewPageQuery = graphql`
|
||||||
query ControlOverviewPageQuery($controlId: ID!) {
|
query ControlOverviewPageQuery($frameworkId: ID!, $controlId: ID!) {
|
||||||
node(id: $controlId) {
|
framework: node(id: $frameworkId) {
|
||||||
|
id
|
||||||
|
... on Framework {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
control: node(id: $controlId) {
|
||||||
id
|
id
|
||||||
... on Control {
|
... on Control {
|
||||||
name
|
name
|
||||||
@@ -45,21 +52,15 @@ function ControlOverviewPageContent({
|
|||||||
controlOverviewPageQuery,
|
controlOverviewPageQuery,
|
||||||
queryRef,
|
queryRef,
|
||||||
);
|
);
|
||||||
const { setBreadcrumbSegment } = useBreadcrumb();
|
const control = data.control;
|
||||||
const control = data.node;
|
const framework = data.framework;
|
||||||
const tasks = control.tasks?.edges.map((edge) => edge?.node) ?? [];
|
const tasks = control?.tasks?.edges.map((edge) => edge?.node) ?? [];
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (control?.name) {
|
|
||||||
setBreadcrumbSegment("frameworks/:frameworkId/:controlId", control.name);
|
|
||||||
}
|
|
||||||
}, [control?.name, setBreadcrumbSegment]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-white p-6 space-y-6">
|
<div className="min-h-screen bg-white p-6 space-y-6">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h1 className="text-2xl font-semibold">{control.name}</h1>
|
<h1 className="text-2xl font-semibold">{control?.name}</h1>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="bg-green-100 text-green-800 px-3 py-1 rounded-full text-sm">
|
<div className="bg-green-100 text-green-800 px-3 py-1 rounded-full text-sm">
|
||||||
30 min
|
30 min
|
||||||
@@ -72,7 +73,7 @@ function ControlOverviewPageContent({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-gray-600 max-w-3xl">{control.description}</p>
|
<p className="text-gray-600 max-w-3xl">{control?.description}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Card className="bg-gray-50 border border-gray-200">
|
<Card className="bg-gray-50 border border-gray-200">
|
||||||
@@ -80,52 +81,64 @@ function ControlOverviewPageContent({
|
|||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<div className="w-4 h-4 rounded-full bg-white flex items-center justify-center border border-gray-200">
|
<div className="w-4 h-4 rounded-full bg-white flex items-center justify-center border border-gray-200">
|
||||||
<div
|
<div
|
||||||
className={`w-2 h-2 rounded-full ${control.state === "IMPLEMENTED" ? "bg-green-500" : "bg-gray-300"}`}
|
className={`w-2 h-2 rounded-full ${
|
||||||
|
control?.state === "IMPLEMENTED"
|
||||||
|
? "bg-green-500"
|
||||||
|
: "bg-gray-300"
|
||||||
|
}`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-sm text-gray-700">
|
<span className="text-sm text-gray-700">
|
||||||
{control.state === "IMPLEMENTED" ? "Validated" : "Not validated"}
|
{control?.state === "IMPLEMENTED" ? "Validated" : "Not validated"}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-xl font-semibold mb-4">Tasks</h2>
|
<h2 className="text-xl font-semibold mb-6">Tasks</h2>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{tasks.map((task) => (
|
{tasks.map((task) => (
|
||||||
<div
|
<div
|
||||||
key={task?.id}
|
key={task?.id}
|
||||||
className="flex items-start gap-3 p-4 bg-white border border-gray-200 rounded-lg hover:shadow-sm transition-shadow"
|
className="flex items-center gap-3 py-4 px-2 hover:bg-gray-50 group"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`mt-1 w-5 h-5 rounded-full border-2 flex items-center justify-center cursor-pointer ${task?.state === "DONE" ? "border-green-500 bg-green-50" : "border-gray-300"}`}
|
className={`w-5 h-5 rounded border flex items-center justify-center cursor-pointer ${
|
||||||
|
task?.state === "DONE"
|
||||||
|
? "border-gray-400 bg-gray-100"
|
||||||
|
: "border-gray-300"
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
{task?.state === "DONE" && (
|
{task?.state === "DONE" && (
|
||||||
<CheckCircle2 className="w-4 h-4 text-green-500" />
|
<CheckCircle2 className="w-4 h-4 text-gray-500" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1">
|
<div className="flex-1 flex items-center justify-between">
|
||||||
<div className="flex items-start justify-between">
|
<div>
|
||||||
<div>
|
<h3
|
||||||
<h3
|
className={`text-sm ${
|
||||||
className={`text-sm font-medium ${task?.state === "DONE" ? "text-gray-500 line-through" : "text-gray-900"}`}
|
task?.state === "DONE"
|
||||||
>
|
? "text-gray-500 line-through"
|
||||||
{task?.name}
|
: "text-gray-900"
|
||||||
</h3>
|
}`}
|
||||||
<p
|
>
|
||||||
className={`text-sm mt-1 ${task?.state === "DONE" ? "text-gray-400" : "text-gray-600"}`}
|
{task?.name}
|
||||||
>
|
</h3>
|
||||||
{task?.description}
|
</div>
|
||||||
</p>
|
<div className="flex items-center gap-2 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||||
</div>
|
<div className="text-gray-400 text-sm">06.00 - 07.30</div>
|
||||||
<div className="flex items-center gap-2">
|
<button className="text-gray-400 hover:text-gray-600">
|
||||||
<div
|
<svg className="w-6 h-6" fill="none" viewBox="0 0 24 24">
|
||||||
className={`px-2 py-1 rounded-full text-xs ${task?.state === "DONE" ? "bg-green-100 text-green-800" : "bg-gray-100 text-gray-800"}`}
|
<path
|
||||||
>
|
stroke="currentColor"
|
||||||
{task?.state.toLowerCase()}
|
strokeLinecap="round"
|
||||||
</div>
|
strokeLinejoin="round"
|
||||||
</div>
|
strokeWidth="2"
|
||||||
|
d="M12 12h.01M12 6h.01M12 18h.01"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -158,14 +171,14 @@ function ControlOverviewPageFallback() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function ControlOverviewPage() {
|
export default function ControlOverviewPage() {
|
||||||
const { controlId } = useParams();
|
const { frameworkId, controlId } = useParams();
|
||||||
const [queryRef, loadQuery] = useQueryLoader<ControlOverviewPageQueryType>(
|
const [queryRef, loadQuery] = useQueryLoader<ControlOverviewPageQueryType>(
|
||||||
controlOverviewPageQuery,
|
controlOverviewPageQuery,
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadQuery({ controlId: controlId! });
|
loadQuery({ frameworkId: frameworkId!, controlId: controlId! });
|
||||||
}, [loadQuery, controlId]);
|
}, [loadQuery, frameworkId, controlId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import { Progress } from "@/components/ui/progress";
|
|||||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||||
import type { FrameworkOverviewPageQuery as FrameworkOverviewPageQueryType } from "./__generated__/FrameworkOverviewPageQuery.graphql";
|
import type { FrameworkOverviewPageQuery as FrameworkOverviewPageQueryType } from "./__generated__/FrameworkOverviewPageQuery.graphql";
|
||||||
import { Helmet } from "react-helmet-async";
|
import { Helmet } from "react-helmet-async";
|
||||||
import { useBreadcrumb } from "@/contexts/BreadcrumbContext";
|
|
||||||
import { createPortal } from "react-dom";
|
import { createPortal } from "react-dom";
|
||||||
|
|
||||||
const FrameworkOverviewPageQuery = graphql`
|
const FrameworkOverviewPageQuery = graphql`
|
||||||
@@ -44,7 +43,6 @@ function FrameworkOverviewPageContent({
|
|||||||
queryRef: PreloadedQuery<FrameworkOverviewPageQueryType>;
|
queryRef: PreloadedQuery<FrameworkOverviewPageQueryType>;
|
||||||
}) {
|
}) {
|
||||||
const data = usePreloadedQuery(FrameworkOverviewPageQuery, queryRef);
|
const data = usePreloadedQuery(FrameworkOverviewPageQuery, queryRef);
|
||||||
const { setBreadcrumbSegment } = useBreadcrumb();
|
|
||||||
const framework = data.node;
|
const framework = data.node;
|
||||||
const controls = framework.controls?.edges.map((edge) => edge?.node) ?? [];
|
const controls = framework.controls?.edges.map((edge) => edge?.node) ?? [];
|
||||||
const [hoveredCard, setHoveredCard] = useState<number | null>(null);
|
const [hoveredCard, setHoveredCard] = useState<number | null>(null);
|
||||||
@@ -57,12 +55,6 @@ function FrameworkOverviewPageContent({
|
|||||||
const [isTooltipHovered, setIsTooltipHovered] = useState(false);
|
const [isTooltipHovered, setIsTooltipHovered] = useState(false);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (framework?.name) {
|
|
||||||
setBreadcrumbSegment("frameworks/:id", framework.name);
|
|
||||||
}
|
|
||||||
}, [framework?.name, setBreadcrumbSegment]);
|
|
||||||
|
|
||||||
// Group controls by their category
|
// Group controls by their category
|
||||||
const controlsByCategory = controls.reduce(
|
const controlsByCategory = controls.reduce(
|
||||||
(acc, control) => {
|
(acc, control) => {
|
||||||
@@ -198,7 +190,7 @@ function FrameworkOverviewPageContent({
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (control?.id) {
|
if (control?.id) {
|
||||||
navigate(
|
navigate(
|
||||||
`/frameworks/${framework.id}/${control.id}`,
|
`/frameworks/${framework.id}/controls/${control.id}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { Button } from "@/components/ui/button";
|
|||||||
import { Link } from "react-router";
|
import { Link } from "react-router";
|
||||||
import { Helmet } from "react-helmet-async";
|
import { Helmet } from "react-helmet-async";
|
||||||
import type { PeopleListPageQuery as PeopleListPageQueryType } from "./__generated__/PeopleListPageQuery.graphql";
|
import type { PeopleListPageQuery as PeopleListPageQueryType } from "./__generated__/PeopleListPageQuery.graphql";
|
||||||
|
import type { PeopleListPageDeletePeopleMutation } from "./__generated__/PeopleListPageDeletePeopleMutation.graphql";
|
||||||
import { PeopleListPagePaginationQuery } from "./__generated__/PeopleListPagePaginationQuery.graphql";
|
import { PeopleListPagePaginationQuery } from "./__generated__/PeopleListPagePaginationQuery.graphql";
|
||||||
import { PeopleListPage_peoples$key } from "./__generated__/PeopleListPage_peoples.graphql";
|
import { PeopleListPage_peoples$key } from "./__generated__/PeopleListPage_peoples.graphql";
|
||||||
|
|
||||||
@@ -132,7 +133,8 @@ function PeopleListContent({
|
|||||||
);
|
);
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
const [isPending, startTransition] = useTransition();
|
const [isPending, startTransition] = useTransition();
|
||||||
const [deletePeople] = useMutation(deletePeopleMutation);
|
const [deletePeople] =
|
||||||
|
useMutation<PeopleListPageDeletePeopleMutation>(deletePeopleMutation);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: peoplesConnection,
|
data: peoplesConnection,
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import { Suspense, useEffect, useState, useCallback } from "react";
|
|||||||
import type { PeopleOverviewPageQuery as PeopleOverviewPageQueryType } from "./__generated__/PeopleOverviewPageQuery.graphql";
|
import type { PeopleOverviewPageQuery as PeopleOverviewPageQueryType } from "./__generated__/PeopleOverviewPageQuery.graphql";
|
||||||
import { useParams } from "react-router";
|
import { useParams } from "react-router";
|
||||||
import { Helmet } from "react-helmet-async";
|
import { Helmet } from "react-helmet-async";
|
||||||
import { useBreadcrumb } from "@/contexts/BreadcrumbContext";
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const peopleOverviewPageQuery = graphql`
|
const peopleOverviewPageQuery = graphql`
|
||||||
@@ -88,7 +87,6 @@ function PeopleOverviewPageContent({
|
|||||||
queryRef: PreloadedQuery<PeopleOverviewPageQueryType>;
|
queryRef: PreloadedQuery<PeopleOverviewPageQueryType>;
|
||||||
}) {
|
}) {
|
||||||
const data = usePreloadedQuery(peopleOverviewPageQuery, queryRef);
|
const data = usePreloadedQuery(peopleOverviewPageQuery, queryRef);
|
||||||
const { setBreadcrumbSegment } = useBreadcrumb();
|
|
||||||
const [editedFields, setEditedFields] = useState<Set<string>>(new Set());
|
const [editedFields, setEditedFields] = useState<Set<string>>(new Set());
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
fullName: data.node.fullName || "",
|
fullName: data.node.fullName || "",
|
||||||
@@ -159,12 +157,6 @@ function PeopleOverviewPageContent({
|
|||||||
setEditedFields(new Set());
|
setEditedFields(new Set());
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (data.node?.fullName) {
|
|
||||||
setBreadcrumbSegment("peoples/:id", data.node.fullName);
|
|
||||||
}
|
|
||||||
}, [data.node?.fullName, setBreadcrumbSegment]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="space-y-6 p-4 md:p-6 lg:p-8">
|
<div className="space-y-6 p-4 md:p-6 lg:p-8">
|
||||||
|
|||||||
@@ -17,9 +17,7 @@ import { Suspense, useEffect, useState, useCallback, useMemo } from "react";
|
|||||||
import type { VendorOverviewPageQuery as VendorOverviewPageQueryType } from "./__generated__/VendorOverviewPageQuery.graphql";
|
import type { VendorOverviewPageQuery as VendorOverviewPageQueryType } from "./__generated__/VendorOverviewPageQuery.graphql";
|
||||||
import { useParams } from "react-router";
|
import { useParams } from "react-router";
|
||||||
import { Helmet } from "react-helmet-async";
|
import { Helmet } from "react-helmet-async";
|
||||||
import { useBreadcrumb } from "@/contexts/BreadcrumbContext";
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import type { UpdateVendorInput } from "./__generated__/VendorOverviewPageUpdateVendorMutation.graphql";
|
|
||||||
|
|
||||||
const vendorOverviewPageQuery = graphql`
|
const vendorOverviewPageQuery = graphql`
|
||||||
query VendorOverviewPageQuery($vendorId: ID!) {
|
query VendorOverviewPageQuery($vendorId: ID!) {
|
||||||
@@ -112,7 +110,6 @@ function VendorOverviewPageContent({
|
|||||||
queryRef: PreloadedQuery<VendorOverviewPageQueryType>;
|
queryRef: PreloadedQuery<VendorOverviewPageQueryType>;
|
||||||
}) {
|
}) {
|
||||||
const data = usePreloadedQuery(vendorOverviewPageQuery, queryRef);
|
const data = usePreloadedQuery(vendorOverviewPageQuery, queryRef);
|
||||||
const { setBreadcrumbSegment } = useBreadcrumb();
|
|
||||||
const [editedFields, setEditedFields] = useState<Set<string>>(new Set());
|
const [editedFields, setEditedFields] = useState<Set<string>>(new Set());
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
name: data.node.name || "",
|
name: data.node.name || "",
|
||||||
@@ -207,12 +204,6 @@ function VendorOverviewPageContent({
|
|||||||
setEditedFields(new Set());
|
setEditedFields(new Set());
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (data.node?.name) {
|
|
||||||
setBreadcrumbSegment("vendors/:id", data.node.name);
|
|
||||||
}
|
|
||||||
}, [data.node?.name, setBreadcrumbSegment]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="space-y-6 p-4 md:p-6 lg:p-8">
|
<div className="space-y-6 p-4 md:p-6 lg:p-8">
|
||||||
|
|||||||
Reference in New Issue
Block a user