Split framework loading in 2 query

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
Jonathan
2025-06-14 21:06:29 +02:00
committed by Bryan Frimin
parent 5396e46781
commit f7fad246e4
20 changed files with 938 additions and 715 deletions

View File

@@ -0,0 +1,26 @@
import { ActionDropdown, Skeleton } from "@probo/ui";
import { Button, IconPencil } from "@probo/ui";
import { useTranslate } from "@probo/i18n";
/**
* Skeleton state for the framework control panel
*/
export function ControlSkeleton() {
const { __ } = useTranslate();
return (
<div className="space-y-6">
<div className="flex justify-between">
<Skeleton style={{ width: 72, height: 34 }} className="mb-3" />
<div className="flex gap-2">
<Button icon={IconPencil} variant="secondary" disabled>
{__("Edit control")}
</Button>
<ActionDropdown variant="secondary" />
</div>
</div>
<Skeleton style={{ width: "80%", height: 24 }} />
<Skeleton style={{ height: 160 }} />
<Skeleton style={{ height: 160 }} />
</div>
);
}

View File

@@ -0,0 +1,5 @@
import { Skeleton } from "@probo/ui";
export function LinkCardSkeleton() {
return <Skeleton style={{ height: 125 }} className="rounded-2xl" />;
}

View File

@@ -88,3 +88,37 @@ export const frameworkNodeQuery = graphql`
}
}
`;
export const frameworkControlNodeQuery = graphql`
query FrameworkGraphControlNodeQuery($controlId: ID!) {
node(id: $controlId) {
... on Control {
id
name
sectionTitle
description
...FrameworkControlDialogFragment
measures(first: 100)
@connection(key: "FrameworkGraphControl_measures") {
__id
edges {
node {
id
...LinkedMeasuresCardFragment
}
}
}
documents(first: 100)
@connection(key: "FrameworkGraphControl_documents") {
__id
edges {
node {
id
...LinkedDocumentsCardFragment
}
}
}
}
}
}
`;

View File

@@ -0,0 +1,508 @@
/**
* @generated SignedSource<<9972eafa19f49d7beea0458c5b9c06d9>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type FrameworkGraphControlNodeQuery$variables = {
controlId: string;
};
export type FrameworkGraphControlNodeQuery$data = {
readonly node: {
readonly description?: string;
readonly documents?: {
readonly __id: string;
readonly edges: ReadonlyArray<{
readonly node: {
readonly id: string;
readonly " $fragmentSpreads": FragmentRefs<"LinkedDocumentsCardFragment">;
};
}>;
};
readonly id?: string;
readonly measures?: {
readonly __id: string;
readonly edges: ReadonlyArray<{
readonly node: {
readonly id: string;
readonly " $fragmentSpreads": FragmentRefs<"LinkedMeasuresCardFragment">;
};
}>;
};
readonly name?: string;
readonly sectionTitle?: string;
readonly " $fragmentSpreads": FragmentRefs<"FrameworkControlDialogFragment">;
};
};
export type FrameworkGraphControlNodeQuery = {
response: FrameworkGraphControlNodeQuery$data;
variables: FrameworkGraphControlNodeQuery$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 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
v4 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "sectionTitle",
"storageKey": null
},
v5 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "description",
"storageKey": null
},
v6 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
},
v7 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "cursor",
"storageKey": null
},
v8 = {
"alias": null,
"args": null,
"concreteType": "PageInfo",
"kind": "LinkedField",
"name": "pageInfo",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "endCursor",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasNextPage",
"storageKey": null
}
],
"storageKey": null
},
v9 = {
"kind": "ClientExtension",
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__id",
"storageKey": null
}
]
},
v10 = [
{
"kind": "Literal",
"name": "first",
"value": 100
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "FrameworkGraphControlNodeQuery",
"selections": [
{
"alias": null,
"args": (v1/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"kind": "InlineFragment",
"selections": [
(v2/*: any*/),
(v3/*: any*/),
(v4/*: any*/),
(v5/*: any*/),
{
"args": null,
"kind": "FragmentSpread",
"name": "FrameworkControlDialogFragment"
},
{
"alias": "measures",
"args": null,
"concreteType": "MeasureConnection",
"kind": "LinkedField",
"name": "__FrameworkGraphControl_measures_connection",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "MeasureEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Measure",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
{
"args": null,
"kind": "FragmentSpread",
"name": "LinkedMeasuresCardFragment"
},
(v6/*: any*/)
],
"storageKey": null
},
(v7/*: any*/)
],
"storageKey": null
},
(v8/*: any*/),
(v9/*: any*/)
],
"storageKey": null
},
{
"alias": "documents",
"args": null,
"concreteType": "DocumentConnection",
"kind": "LinkedField",
"name": "__FrameworkGraphControl_documents_connection",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "DocumentEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Document",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
{
"args": null,
"kind": "FragmentSpread",
"name": "LinkedDocumentsCardFragment"
},
(v6/*: any*/)
],
"storageKey": null
},
(v7/*: any*/)
],
"storageKey": null
},
(v8/*: any*/),
(v9/*: any*/)
],
"storageKey": null
}
],
"type": "Control",
"abstractKey": null
}
],
"storageKey": null
}
],
"type": "Query",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "FrameworkGraphControlNodeQuery",
"selections": [
{
"alias": null,
"args": (v1/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v6/*: any*/),
(v2/*: any*/),
{
"kind": "InlineFragment",
"selections": [
(v3/*: any*/),
(v4/*: any*/),
(v5/*: any*/),
{
"alias": null,
"args": (v10/*: any*/),
"concreteType": "MeasureConnection",
"kind": "LinkedField",
"name": "measures",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "MeasureEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Measure",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
(v3/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "state",
"storageKey": null
},
(v6/*: any*/)
],
"storageKey": null
},
(v7/*: any*/)
],
"storageKey": null
},
(v8/*: any*/),
(v9/*: any*/)
],
"storageKey": "measures(first:100)"
},
{
"alias": null,
"args": (v10/*: any*/),
"filters": null,
"handle": "connection",
"key": "FrameworkGraphControl_measures",
"kind": "LinkedHandle",
"name": "measures"
},
{
"alias": null,
"args": (v10/*: any*/),
"concreteType": "DocumentConnection",
"kind": "LinkedField",
"name": "documents",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "DocumentEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Document",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "title",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "documentType",
"storageKey": null
},
{
"alias": null,
"args": [
{
"kind": "Literal",
"name": "first",
"value": 1
}
],
"concreteType": "DocumentVersionConnection",
"kind": "LinkedField",
"name": "versions",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "DocumentVersionEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "DocumentVersion",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "status",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "versions(first:1)"
},
(v6/*: any*/)
],
"storageKey": null
},
(v7/*: any*/)
],
"storageKey": null
},
(v8/*: any*/),
(v9/*: any*/)
],
"storageKey": "documents(first:100)"
},
{
"alias": null,
"args": (v10/*: any*/),
"filters": null,
"handle": "connection",
"key": "FrameworkGraphControl_documents",
"kind": "LinkedHandle",
"name": "documents"
}
],
"type": "Control",
"abstractKey": null
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "165ee5f4a3579e54fb7acafd6fd7ab12",
"id": null,
"metadata": {
"connection": [
{
"count": null,
"cursor": null,
"direction": "forward",
"path": [
"node",
"measures"
]
},
{
"count": null,
"cursor": null,
"direction": "forward",
"path": [
"node",
"documents"
]
}
]
},
"name": "FrameworkGraphControlNodeQuery",
"operationKind": "query",
"text": "query FrameworkGraphControlNodeQuery(\n $controlId: ID!\n) {\n node(id: $controlId) {\n __typename\n ... on Control {\n id\n name\n sectionTitle\n description\n ...FrameworkControlDialogFragment\n measures(first: 100) {\n edges {\n node {\n id\n ...LinkedMeasuresCardFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n documents(first: 100) {\n edges {\n node {\n id\n ...LinkedDocumentsCardFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n id\n }\n}\n\nfragment FrameworkControlDialogFragment on Control {\n id\n name\n description\n sectionTitle\n}\n\nfragment LinkedDocumentsCardFragment on Document {\n id\n title\n createdAt\n documentType\n versions(first: 1) {\n edges {\n node {\n id\n status\n }\n }\n }\n}\n\nfragment LinkedMeasuresCardFragment on Measure {\n id\n name\n state\n}\n"
}
};
})();
(node as any).hash = "a2b0b5449af380ea2843aab6ce187b75";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<5cb97bc6e333575149d6cc4418370d45>>
* @generated SignedSource<<5627e402cef1ec879c133d54980b26ab>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -53,71 +53,6 @@ v3 = {
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
v4 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
},
v5 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "description",
"storageKey": null
},
v6 = [
{
"kind": "Literal",
"name": "first",
"value": 100
}
],
v7 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "cursor",
"storageKey": null
},
v8 = {
"alias": null,
"args": null,
"concreteType": "PageInfo",
"kind": "LinkedField",
"name": "pageInfo",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "endCursor",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasNextPage",
"storageKey": null
}
],
"storageKey": null
},
v9 = {
"kind": "ClientExtension",
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__id",
"storageKey": null
}
]
};
return {
"fragment": {
@@ -169,16 +104,34 @@ return {
"name": "node",
"plural": false,
"selections": [
(v4/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
},
(v2/*: any*/),
{
"kind": "InlineFragment",
"selections": [
(v3/*: any*/),
(v5/*: any*/),
{
"alias": null,
"args": (v6/*: any*/),
"args": null,
"kind": "ScalarField",
"name": "description",
"storageKey": null
},
{
"alias": null,
"args": [
{
"kind": "Literal",
"name": "first",
"value": 100
}
],
"concreteType": "ControlConnection",
"kind": "LinkedField",
"name": "controls",
@@ -208,185 +161,25 @@ return {
"name": "sectionTitle",
"storageKey": null
},
(v3/*: any*/),
(v5/*: any*/),
{
"alias": null,
"args": (v6/*: any*/),
"concreteType": "MeasureConnection",
"kind": "LinkedField",
"name": "measures",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "MeasureEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Measure",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
(v3/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "state",
"storageKey": null
},
(v4/*: any*/)
],
"storageKey": null
},
(v7/*: any*/)
],
"storageKey": null
},
(v8/*: any*/),
(v9/*: any*/)
],
"storageKey": "measures(first:100)"
},
{
"alias": null,
"args": (v6/*: any*/),
"filters": null,
"handle": "connection",
"key": "FrameworkDetailPage_measures",
"kind": "LinkedHandle",
"name": "measures"
},
{
"alias": null,
"args": (v6/*: any*/),
"concreteType": "DocumentConnection",
"kind": "LinkedField",
"name": "documents",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "DocumentEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Document",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "title",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "documentType",
"storageKey": null
},
{
"alias": null,
"args": [
{
"kind": "Literal",
"name": "first",
"value": 1
}
],
"concreteType": "DocumentVersionConnection",
"kind": "LinkedField",
"name": "versions",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "DocumentVersionEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "DocumentVersion",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "status",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "versions(first:1)"
},
(v4/*: any*/)
],
"storageKey": null
},
(v7/*: any*/)
],
"storageKey": null
},
(v8/*: any*/),
(v9/*: any*/)
],
"storageKey": "documents(first:100)"
},
{
"alias": null,
"args": (v6/*: any*/),
"filters": null,
"handle": "connection",
"key": "FrameworkDetailPage_documents",
"kind": "LinkedHandle",
"name": "documents"
}
(v3/*: any*/)
],
"storageKey": null
}
],
"storageKey": null
},
(v9/*: any*/)
{
"kind": "ClientExtension",
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__id",
"storageKey": null
}
]
}
],
"storageKey": "controls(first:100)"
}
@@ -400,12 +193,12 @@ return {
]
},
"params": {
"cacheID": "4b21f0ec7a74eed125d96e4cfc7f94fd",
"cacheID": "28ea34e7b455cffd5283428ef567963a",
"id": null,
"metadata": {},
"name": "FrameworkGraphNodeQuery",
"operationKind": "query",
"text": "query FrameworkGraphNodeQuery(\n $frameworkId: ID!\n) {\n node(id: $frameworkId) {\n __typename\n ... on Framework {\n id\n name\n ...FrameworkDetailPageFragment\n }\n id\n }\n}\n\nfragment FrameworkControlDialogFragment on Control {\n id\n name\n description\n sectionTitle\n}\n\nfragment FrameworkDetailPageFragment on Framework {\n id\n name\n description\n controls(first: 100) {\n edges {\n node {\n id\n sectionTitle\n name\n description\n ...FrameworkControlDialogFragment\n measures(first: 100) {\n edges {\n node {\n id\n ...LinkedMeasuresCardFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n documents(first: 100) {\n edges {\n node {\n id\n ...LinkedDocumentsCardFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n }\n}\n\nfragment LinkedDocumentsCardFragment on Document {\n id\n title\n createdAt\n documentType\n versions(first: 1) {\n edges {\n node {\n id\n status\n }\n }\n }\n}\n\nfragment LinkedMeasuresCardFragment on Measure {\n id\n name\n state\n}\n"
"text": "query FrameworkGraphNodeQuery(\n $frameworkId: ID!\n) {\n node(id: $frameworkId) {\n __typename\n ... on Framework {\n id\n name\n ...FrameworkDetailPageFragment\n }\n id\n }\n}\n\nfragment FrameworkDetailPageFragment on Framework {\n id\n name\n description\n controls(first: 100) {\n edges {\n node {\n id\n sectionTitle\n name\n }\n }\n }\n}\n"
}
};
})();

View File

@@ -0,0 +1,195 @@
import {
useMutation,
usePreloadedQuery,
type PreloadedQuery,
} from "react-relay";
import { graphql } from "relay-runtime";
import {
ActionDropdown,
Button,
DropdownItem,
IconPencil,
IconTrashCan,
useConfirm,
} from "@probo/ui";
import { useTranslate } from "@probo/i18n";
import { LinkedMeasuresCard } from "/components/measures/LinkedMeasuresCard";
import { useNavigate, useOutletContext } from "react-router";
import { useOrganizationId } from "/hooks/useOrganizationId";
import { LinkedDocumentsCard } from "/components/documents/LinkedDocumentsCard";
import { FrameworkControlDialog } from "./dialogs/FrameworkControlDialog";
import { promisifyMutation } from "@probo/helpers";
import type { FrameworkGraphControlNodeQuery } from "/hooks/graph/__generated__/FrameworkGraphControlNodeQuery.graphql";
import { frameworkControlNodeQuery } from "/hooks/graph/FrameworkGraph";
import type { FrameworkDetailPageFragment$data } from "./__generated__/FrameworkDetailPageFragment.graphql";
const attachMeasureMutation = graphql`
mutation FrameworkControlPageAttachMutation(
$input: CreateControlMeasureMappingInput!
$connections: [ID!]!
) {
createControlMeasureMapping(input: $input) {
measureEdge @prependEdge(connections: $connections) {
node {
id
...LinkedMeasuresCardFragment
}
}
}
}
`;
const detachMeasureMutation = graphql`
mutation FrameworkControlPageDetachMutation(
$input: DeleteControlMeasureMappingInput!
$connections: [ID!]!
) {
deleteControlMeasureMapping(input: $input) {
deletedMeasureId @deleteEdge(connections: $connections)
}
}
`;
const attachDocumentMutation = graphql`
mutation FrameworkControlPageAttachDocumentMutation(
$input: CreateControlDocumentMappingInput!
$connections: [ID!]!
) {
createControlDocumentMapping(input: $input) {
documentEdge @prependEdge(connections: $connections) {
node {
id
...LinkedDocumentsCardFragment
}
}
}
}
`;
const detachDocumentMutation = graphql`
mutation FrameworkControlPageDetachDocumentMutation(
$input: DeleteControlDocumentMappingInput!
$connections: [ID!]!
) {
deleteControlDocumentMapping(input: $input) {
deletedDocumentId @deleteEdge(connections: $connections)
}
}
`;
const deleteControlMutation = graphql`
mutation FrameworkControlPageDeleteControlMutation(
$input: DeleteControlInput!
$connections: [ID!]!
) {
deleteControl(input: $input) {
deletedControlId @deleteEdge(connections: $connections)
}
}
`;
type Props = {
queryRef: PreloadedQuery<FrameworkGraphControlNodeQuery>;
};
/**
* Display the control detail on the right panel
*/
export default function FrameworkControlPage({ queryRef }: Props) {
const { __ } = useTranslate();
const { framework } = useOutletContext<{
framework: FrameworkDetailPageFragment$data;
}>();
const connectionId = framework.controls.__id;
const control = usePreloadedQuery(frameworkControlNodeQuery, queryRef).node;
const organizationId = useOrganizationId();
const confirm = useConfirm();
const navigate = useNavigate();
// Mutations
const [detachMeasure, isDetachingMeasure] = useMutation(
detachMeasureMutation
);
const [attachMeasure, isAttachingMeasure] = useMutation(
attachMeasureMutation
);
const [detachDocument, isDetachingDocument] = useMutation(
detachDocumentMutation
);
const [attachDocument, isAttachingDocument] = useMutation(
attachDocumentMutation
);
const [deleteControl] = useMutation(deleteControlMutation);
const onDelete = () => {
confirm(
() => {
return promisifyMutation(deleteControl)({
variables: {
input: {
controlId: control.id,
},
connections: [connectionId],
},
onCompleted: () => {
navigate(
`/organizations/${organizationId}/frameworks/${framework.id}`
);
},
});
},
{
message: __("Are you sure you want to delete this control?"),
}
);
};
return (
<div className="space-y-6">
<div className="flex justify-between">
<div className="text-xl font-medium px-[6px] py-[2px] border border-border-low rounded-lg w-max bg-active mb-3">
{control.sectionTitle}
</div>
<div className="flex gap-2">
<FrameworkControlDialog
frameworkId={framework.id}
connectionId={connectionId}
control={control}
>
<Button icon={IconPencil} variant="secondary">
{__("Edit control")}
</Button>
</FrameworkControlDialog>
<ActionDropdown variant="secondary">
<DropdownItem
icon={IconTrashCan}
variant="danger"
onClick={onDelete}
>
{__("Delete")}
</DropdownItem>
</ActionDropdown>
</div>
</div>
<div className="text-base">{control.name}</div>
<LinkedMeasuresCard
variant="card"
measures={control.measures?.edges.map((edge) => edge.node) ?? []}
params={{ controlId: control.id }}
connectionId={control.measures?.__id!}
onAttach={attachMeasure}
onDetach={detachMeasure}
disabled={isAttachingMeasure || isDetachingMeasure}
/>
<LinkedDocumentsCard
variant="card"
documents={control.documents?.edges.map((edge) => edge.node) ?? []}
params={{ controlId: control.id }}
connectionId={control.documents?.__id!}
onAttach={attachDocument}
onDetach={detachDocument}
disabled={isAttachingDocument || isDetachingDocument}
/>
</div>
);
}

View File

@@ -1,6 +1,5 @@
import {
useFragment,
useMutation,
usePreloadedQuery,
type PreloadedQuery,
} from "react-relay";
@@ -15,7 +14,6 @@ import {
IconPlusLarge,
IconTrashCan,
PageHeader,
useConfirm,
} from "@probo/ui";
import { FrameworkLogo } from "/components/FrameworkLogo";
import {
@@ -24,20 +22,12 @@ import {
useDeleteFrameworkMutation,
} from "/hooks/graph/FrameworkGraph";
import { useTranslate } from "@probo/i18n";
import { LinkedMeasuresCard } from "/components/measures/LinkedMeasuresCard";
import { useNavigate, useParams } from "react-router";
import { Navigate, Outlet, useNavigate, useParams } from "react-router";
import { useOrganizationId } from "/hooks/useOrganizationId";
import type { FrameworkGraphNodeQuery } from "/hooks/graph/__generated__/FrameworkGraphNodeQuery.graphql";
import type {
FrameworkDetailPageFragment$data,
FrameworkDetailPageFragment$key,
} from "./__generated__/FrameworkDetailPageFragment.graphql";
import { LinkedDocumentsCard } from "/components/documents/LinkedDocumentsCard";
import type { FrameworkDetailPageFragment$key } from "./__generated__/FrameworkDetailPageFragment.graphql";
import { FrameworkFormDialog } from "./dialogs/FrameworkFormDialog";
import { FrameworkControlDialog } from "./dialogs/FrameworkControlDialog";
import type { FrameworkControlDialogFragment$key } from "./dialogs/__generated__/FrameworkControlDialogFragment.graphql";
import type { NodeOf } from "/types";
import { promisifyMutation } from "@probo/helpers";
const frameworkDetailFragment = graphql`
fragment FrameworkDetailPageFragment on Framework {
@@ -51,88 +41,12 @@ const frameworkDetailFragment = graphql`
id
sectionTitle
name
description
...FrameworkControlDialogFragment
measures(first: 100)
@connection(key: "FrameworkDetailPage_measures") {
__id
edges {
node {
id
...LinkedMeasuresCardFragment
}
}
}
documents(first: 100)
@connection(key: "FrameworkDetailPage_documents") {
__id
edges {
node {
id
...LinkedDocumentsCardFragment
}
}
}
}
}
}
}
`;
const attachMeasureMutation = graphql`
mutation FrameworkDetailPageAttachMutation(
$input: CreateControlMeasureMappingInput!
$connections: [ID!]!
) {
createControlMeasureMapping(input: $input) {
measureEdge @prependEdge(connections: $connections) {
node {
id
...LinkedMeasuresCardFragment
}
}
}
}
`;
const detachMeasureMutation = graphql`
mutation FrameworkDetailPageDetachMutation(
$input: DeleteControlMeasureMappingInput!
$connections: [ID!]!
) {
deleteControlMeasureMapping(input: $input) {
deletedMeasureId @deleteEdge(connections: $connections)
}
}
`;
const attachDocumentMutation = graphql`
mutation FrameworkDetailPageAttachDocumentMutation(
$input: CreateControlDocumentMappingInput!
$connections: [ID!]!
) {
createControlDocumentMapping(input: $input) {
documentEdge @prependEdge(connections: $connections) {
node {
id
...LinkedDocumentsCardFragment
}
}
}
}
`;
const detachDocumentMutation = graphql`
mutation FrameworkDetailPageDetachDocumentMutation(
$input: DeleteControlDocumentMappingInput!
$connections: [ID!]!
) {
deleteControlDocumentMapping(input: $input) {
deletedDocumentId @deleteEdge(connections: $connections)
}
}
`;
type Props = {
queryRef: PreloadedQuery<FrameworkGraphNodeQuery>;
};
@@ -166,6 +80,14 @@ export default function FrameworkDetailPage(props: Props) {
});
};
if (!controlId) {
return (
<Navigate
to={`/organizations/${organizationId}/frameworks/${framework.id}/controls/${controls[0].id}`}
/>
);
}
return (
<div className="space-y-6">
<PageHeader
@@ -202,7 +124,7 @@ export default function FrameworkDetailPage(props: Props) {
<ControlItem
key={control.id}
id={control.sectionTitle}
description={control.name ?? control.description}
description={control.name}
to={`/organizations/${organizationId}/frameworks/${framework.id}/controls/${control.id}`}
active={selectedControl?.id === control.id}
/>
@@ -217,137 +139,8 @@ export default function FrameworkDetailPage(props: Props) {
</button>
</FrameworkControlDialog>
</div>
{selectedControl ? (
<ControlContent
control={selectedControl}
frameworkId={framework.id}
connectionId={connectionId}
/>
) : (
<div className="h-full flex items-center justify-center">
<div className="text-sm text-txt-secondary">
{__("No control selected")}
</div>
</div>
)}
<Outlet context={{ framework }} />
</div>
</div>
);
}
const deleteControlMutation = graphql`
mutation FrameworkDetailPageDeleteControlMutation(
$input: DeleteControlInput!
$connections: [ID!]!
) {
deleteControl(input: $input) {
deletedControlId @deleteEdge(connections: $connections)
}
}
`;
/**
* Right side pannel (showing the content of the selected control)
*/
function ControlContent({
control,
frameworkId,
connectionId,
}: {
control: NodeOf<FrameworkDetailPageFragment$data["controls"]> &
FrameworkControlDialogFragment$key;
frameworkId: string;
connectionId: string;
}) {
const { __ } = useTranslate();
const organizationId = useOrganizationId();
const confirm = useConfirm();
const navigate = useNavigate();
// Mutations
const [detachMeasure, isDetachingMeasure] = useMutation(
detachMeasureMutation
);
const [attachMeasure, isAttachingMeasure] = useMutation(
attachMeasureMutation
);
const [detachDocument, isDetachingDocument] = useMutation(
detachDocumentMutation
);
const [attachDocument, isAttachingDocument] = useMutation(
attachDocumentMutation
);
const [deleteControl] = useMutation(deleteControlMutation);
const onDelete = () => {
confirm(
() => {
return promisifyMutation(deleteControl)({
variables: {
input: {
controlId: control.id,
},
connections: [connectionId],
},
onCompleted: () => {
navigate(
`/organizations/${organizationId}/frameworks/${frameworkId}`
);
},
});
},
{
message: __("Are you sure you want to delete this control?"),
}
);
};
return (
<div className="space-y-6">
<div className="flex justify-between">
<div className="text-xl font-medium px-[6px] py-[2px] border border-border-low rounded-lg w-max bg-active mb-3">
{control.sectionTitle}
</div>
<div className="flex gap-2">
<FrameworkControlDialog
frameworkId={frameworkId}
connectionId={connectionId}
control={control}
>
<Button icon={IconPencil} variant="secondary">
{__("Edit control")}
</Button>
</FrameworkControlDialog>
<ActionDropdown variant="secondary">
<DropdownItem
icon={IconTrashCan}
variant="danger"
onClick={onDelete}
>
{__("Delete")}
</DropdownItem>
</ActionDropdown>
</div>
</div>
<div className="text-base">{control.name}</div>
<LinkedMeasuresCard
variant="card"
measures={control?.measures.edges.map((edge) => edge.node) ?? []}
params={{ controlId: control.id }}
connectionId={control.measures.__id!}
onAttach={attachMeasure}
onDetach={detachMeasure}
disabled={isAttachingMeasure || isDetachingMeasure}
/>
<LinkedDocumentsCard
variant="card"
documents={control?.documents.edges.map((edge) => edge.node) ?? []}
params={{ controlId: control.id }}
connectionId={control.documents.__id!}
onAttach={attachDocument}
onDetach={detachDocument}
disabled={isAttachingDocument || isDetachingDocument}
/>
</div>
);
}

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<7d4a807c299030c97164951cbe8ef067>>
* @generated SignedSource<<47d493f6b225797004c11ba908c1e7d2>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -14,11 +14,11 @@ export type CreateControlDocumentMappingInput = {
controlId: string;
documentId: string;
};
export type FrameworkDetailPageAttachDocumentMutation$variables = {
export type FrameworkControlPageAttachDocumentMutation$variables = {
connections: ReadonlyArray<string>;
input: CreateControlDocumentMappingInput;
};
export type FrameworkDetailPageAttachDocumentMutation$data = {
export type FrameworkControlPageAttachDocumentMutation$data = {
readonly createControlDocumentMapping: {
readonly documentEdge: {
readonly node: {
@@ -28,9 +28,9 @@ export type FrameworkDetailPageAttachDocumentMutation$data = {
};
};
};
export type FrameworkDetailPageAttachDocumentMutation = {
response: FrameworkDetailPageAttachDocumentMutation$data;
variables: FrameworkDetailPageAttachDocumentMutation$variables;
export type FrameworkControlPageAttachDocumentMutation = {
response: FrameworkControlPageAttachDocumentMutation$data;
variables: FrameworkControlPageAttachDocumentMutation$variables;
};
const node: ConcreteRequest = (function(){
@@ -66,7 +66,7 @@ return {
],
"kind": "Fragment",
"metadata": null,
"name": "FrameworkDetailPageAttachDocumentMutation",
"name": "FrameworkControlPageAttachDocumentMutation",
"selections": [
{
"alias": null,
@@ -118,7 +118,7 @@ return {
(v0/*: any*/)
],
"kind": "Operation",
"name": "FrameworkDetailPageAttachDocumentMutation",
"name": "FrameworkControlPageAttachDocumentMutation",
"selections": [
{
"alias": null,
@@ -241,16 +241,16 @@ return {
]
},
"params": {
"cacheID": "968bd0beeabab861ca610804640e5e11",
"cacheID": "857c472a56bba83b8249bb6f3b97eea2",
"id": null,
"metadata": {},
"name": "FrameworkDetailPageAttachDocumentMutation",
"name": "FrameworkControlPageAttachDocumentMutation",
"operationKind": "mutation",
"text": "mutation FrameworkDetailPageAttachDocumentMutation(\n $input: CreateControlDocumentMappingInput!\n) {\n createControlDocumentMapping(input: $input) {\n documentEdge {\n node {\n id\n ...LinkedDocumentsCardFragment\n }\n }\n }\n}\n\nfragment LinkedDocumentsCardFragment on Document {\n id\n title\n createdAt\n documentType\n versions(first: 1) {\n edges {\n node {\n id\n status\n }\n }\n }\n}\n"
"text": "mutation FrameworkControlPageAttachDocumentMutation(\n $input: CreateControlDocumentMappingInput!\n) {\n createControlDocumentMapping(input: $input) {\n documentEdge {\n node {\n id\n ...LinkedDocumentsCardFragment\n }\n }\n }\n}\n\nfragment LinkedDocumentsCardFragment on Document {\n id\n title\n createdAt\n documentType\n versions(first: 1) {\n edges {\n node {\n id\n status\n }\n }\n }\n}\n"
}
};
})();
(node as any).hash = "26c53db439feba2b2d65c3e3a9a973c7";
(node as any).hash = "6f019fa6bdfccc4cddd80f0de770440f";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<81d008fc4116a78a92e2d925f8d6daa7>>
* @generated SignedSource<<1d23c8e1a8e16a9811d75d208ade0889>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -14,11 +14,11 @@ export type CreateControlMeasureMappingInput = {
controlId: string;
measureId: string;
};
export type FrameworkDetailPageAttachMutation$variables = {
export type FrameworkControlPageAttachMutation$variables = {
connections: ReadonlyArray<string>;
input: CreateControlMeasureMappingInput;
};
export type FrameworkDetailPageAttachMutation$data = {
export type FrameworkControlPageAttachMutation$data = {
readonly createControlMeasureMapping: {
readonly measureEdge: {
readonly node: {
@@ -28,9 +28,9 @@ export type FrameworkDetailPageAttachMutation$data = {
};
};
};
export type FrameworkDetailPageAttachMutation = {
response: FrameworkDetailPageAttachMutation$data;
variables: FrameworkDetailPageAttachMutation$variables;
export type FrameworkControlPageAttachMutation = {
response: FrameworkControlPageAttachMutation$data;
variables: FrameworkControlPageAttachMutation$variables;
};
const node: ConcreteRequest = (function(){
@@ -66,7 +66,7 @@ return {
],
"kind": "Fragment",
"metadata": null,
"name": "FrameworkDetailPageAttachMutation",
"name": "FrameworkControlPageAttachMutation",
"selections": [
{
"alias": null,
@@ -118,7 +118,7 @@ return {
(v0/*: any*/)
],
"kind": "Operation",
"name": "FrameworkDetailPageAttachMutation",
"name": "FrameworkControlPageAttachMutation",
"selections": [
{
"alias": null,
@@ -187,16 +187,16 @@ return {
]
},
"params": {
"cacheID": "9fcb599cd78f3108d6d71aa571d7005d",
"cacheID": "85f870b18745fcd28bac3a52c404e98f",
"id": null,
"metadata": {},
"name": "FrameworkDetailPageAttachMutation",
"name": "FrameworkControlPageAttachMutation",
"operationKind": "mutation",
"text": "mutation FrameworkDetailPageAttachMutation(\n $input: CreateControlMeasureMappingInput!\n) {\n createControlMeasureMapping(input: $input) {\n measureEdge {\n node {\n id\n ...LinkedMeasuresCardFragment\n }\n }\n }\n}\n\nfragment LinkedMeasuresCardFragment on Measure {\n id\n name\n state\n}\n"
"text": "mutation FrameworkControlPageAttachMutation(\n $input: CreateControlMeasureMappingInput!\n) {\n createControlMeasureMapping(input: $input) {\n measureEdge {\n node {\n id\n ...LinkedMeasuresCardFragment\n }\n }\n }\n}\n\nfragment LinkedMeasuresCardFragment on Measure {\n id\n name\n state\n}\n"
}
};
})();
(node as any).hash = "d3834b8c2656d347e13189469894be61";
(node as any).hash = "e52f26e6906aa014ad020bd6f23a75d6";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<ed7ba35de62460b7625419b78b67997b>>
* @generated SignedSource<<87a74ac3a3c2523bd65512703dc2246e>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -12,18 +12,18 @@ import { ConcreteRequest } from 'relay-runtime';
export type DeleteControlInput = {
controlId: string;
};
export type FrameworkDetailPageDeleteControlMutation$variables = {
export type FrameworkControlPageDeleteControlMutation$variables = {
connections: ReadonlyArray<string>;
input: DeleteControlInput;
};
export type FrameworkDetailPageDeleteControlMutation$data = {
export type FrameworkControlPageDeleteControlMutation$data = {
readonly deleteControl: {
readonly deletedControlId: string;
};
};
export type FrameworkDetailPageDeleteControlMutation = {
response: FrameworkDetailPageDeleteControlMutation$data;
variables: FrameworkDetailPageDeleteControlMutation$variables;
export type FrameworkControlPageDeleteControlMutation = {
response: FrameworkControlPageDeleteControlMutation$data;
variables: FrameworkControlPageDeleteControlMutation$variables;
};
const node: ConcreteRequest = (function(){
@@ -59,7 +59,7 @@ return {
],
"kind": "Fragment",
"metadata": null,
"name": "FrameworkDetailPageDeleteControlMutation",
"name": "FrameworkControlPageDeleteControlMutation",
"selections": [
{
"alias": null,
@@ -84,7 +84,7 @@ return {
(v0/*: any*/)
],
"kind": "Operation",
"name": "FrameworkDetailPageDeleteControlMutation",
"name": "FrameworkControlPageDeleteControlMutation",
"selections": [
{
"alias": null,
@@ -117,16 +117,16 @@ return {
]
},
"params": {
"cacheID": "d86c7a2bd535a98d1cb7308a96c13401",
"cacheID": "f5559c3bf33da22ba6cb83fc408426e0",
"id": null,
"metadata": {},
"name": "FrameworkDetailPageDeleteControlMutation",
"name": "FrameworkControlPageDeleteControlMutation",
"operationKind": "mutation",
"text": "mutation FrameworkDetailPageDeleteControlMutation(\n $input: DeleteControlInput!\n) {\n deleteControl(input: $input) {\n deletedControlId\n }\n}\n"
"text": "mutation FrameworkControlPageDeleteControlMutation(\n $input: DeleteControlInput!\n) {\n deleteControl(input: $input) {\n deletedControlId\n }\n}\n"
}
};
})();
(node as any).hash = "a067e712c3c51b73abac50e6c3779141";
(node as any).hash = "c92c75d0666d2a60d3a2fcfb2959b2e9";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<6f2850042c0413963bd82f90236fe220>>
* @generated SignedSource<<61b6714faa71bf0afe54a1984b719a55>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -13,18 +13,18 @@ export type DeleteControlDocumentMappingInput = {
controlId: string;
documentId: string;
};
export type FrameworkDetailPageDetachDocumentMutation$variables = {
export type FrameworkControlPageDetachDocumentMutation$variables = {
connections: ReadonlyArray<string>;
input: DeleteControlDocumentMappingInput;
};
export type FrameworkDetailPageDetachDocumentMutation$data = {
export type FrameworkControlPageDetachDocumentMutation$data = {
readonly deleteControlDocumentMapping: {
readonly deletedDocumentId: string;
};
};
export type FrameworkDetailPageDetachDocumentMutation = {
response: FrameworkDetailPageDetachDocumentMutation$data;
variables: FrameworkDetailPageDetachDocumentMutation$variables;
export type FrameworkControlPageDetachDocumentMutation = {
response: FrameworkControlPageDetachDocumentMutation$data;
variables: FrameworkControlPageDetachDocumentMutation$variables;
};
const node: ConcreteRequest = (function(){
@@ -60,7 +60,7 @@ return {
],
"kind": "Fragment",
"metadata": null,
"name": "FrameworkDetailPageDetachDocumentMutation",
"name": "FrameworkControlPageDetachDocumentMutation",
"selections": [
{
"alias": null,
@@ -85,7 +85,7 @@ return {
(v0/*: any*/)
],
"kind": "Operation",
"name": "FrameworkDetailPageDetachDocumentMutation",
"name": "FrameworkControlPageDetachDocumentMutation",
"selections": [
{
"alias": null,
@@ -118,16 +118,16 @@ return {
]
},
"params": {
"cacheID": "53618baa1678e3a1b496a08597bdd364",
"cacheID": "f442693991b055c11609fb7bc326f1da",
"id": null,
"metadata": {},
"name": "FrameworkDetailPageDetachDocumentMutation",
"name": "FrameworkControlPageDetachDocumentMutation",
"operationKind": "mutation",
"text": "mutation FrameworkDetailPageDetachDocumentMutation(\n $input: DeleteControlDocumentMappingInput!\n) {\n deleteControlDocumentMapping(input: $input) {\n deletedDocumentId\n }\n}\n"
"text": "mutation FrameworkControlPageDetachDocumentMutation(\n $input: DeleteControlDocumentMappingInput!\n) {\n deleteControlDocumentMapping(input: $input) {\n deletedDocumentId\n }\n}\n"
}
};
})();
(node as any).hash = "233b182104744a67a21d21d60b6df67a";
(node as any).hash = "8c20b51090f69e932bc4801d88a6eaa6";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<ff996c0902be9e7b8c23c9dd3d0f3759>>
* @generated SignedSource<<40a72ad7f2eb9fc26bf12c7f4ce731d7>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -13,18 +13,18 @@ export type DeleteControlMeasureMappingInput = {
controlId: string;
measureId: string;
};
export type FrameworkDetailPageDetachMutation$variables = {
export type FrameworkControlPageDetachMutation$variables = {
connections: ReadonlyArray<string>;
input: DeleteControlMeasureMappingInput;
};
export type FrameworkDetailPageDetachMutation$data = {
export type FrameworkControlPageDetachMutation$data = {
readonly deleteControlMeasureMapping: {
readonly deletedMeasureId: string;
};
};
export type FrameworkDetailPageDetachMutation = {
response: FrameworkDetailPageDetachMutation$data;
variables: FrameworkDetailPageDetachMutation$variables;
export type FrameworkControlPageDetachMutation = {
response: FrameworkControlPageDetachMutation$data;
variables: FrameworkControlPageDetachMutation$variables;
};
const node: ConcreteRequest = (function(){
@@ -60,7 +60,7 @@ return {
],
"kind": "Fragment",
"metadata": null,
"name": "FrameworkDetailPageDetachMutation",
"name": "FrameworkControlPageDetachMutation",
"selections": [
{
"alias": null,
@@ -85,7 +85,7 @@ return {
(v0/*: any*/)
],
"kind": "Operation",
"name": "FrameworkDetailPageDetachMutation",
"name": "FrameworkControlPageDetachMutation",
"selections": [
{
"alias": null,
@@ -118,16 +118,16 @@ return {
]
},
"params": {
"cacheID": "0bede5dc29caa4e2aba9b0dd96e58014",
"cacheID": "ce39a8bbb51cd7437dbc08612b60a214",
"id": null,
"metadata": {},
"name": "FrameworkDetailPageDetachMutation",
"name": "FrameworkControlPageDetachMutation",
"operationKind": "mutation",
"text": "mutation FrameworkDetailPageDetachMutation(\n $input: DeleteControlMeasureMappingInput!\n) {\n deleteControlMeasureMapping(input: $input) {\n deletedMeasureId\n }\n}\n"
"text": "mutation FrameworkControlPageDetachMutation(\n $input: DeleteControlMeasureMappingInput!\n) {\n deleteControlMeasureMapping(input: $input) {\n deletedMeasureId\n }\n}\n"
}
};
})();
(node as any).hash = "96945bf8e909e7425af2297f7ee6ea0a";
(node as any).hash = "c62748aaf06f0050732be91ae341ae73";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<775e1361b15523efe196b9edbd406f5a>>
* @generated SignedSource<<6b38711542a158ba9424c744d217bed5>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -15,29 +15,9 @@ export type FrameworkDetailPageFragment$data = {
readonly __id: string;
readonly edges: ReadonlyArray<{
readonly node: {
readonly description: string;
readonly documents: {
readonly __id: string;
readonly edges: ReadonlyArray<{
readonly node: {
readonly id: string;
readonly " $fragmentSpreads": FragmentRefs<"LinkedDocumentsCardFragment">;
};
}>;
};
readonly id: string;
readonly measures: {
readonly __id: string;
readonly edges: ReadonlyArray<{
readonly node: {
readonly id: string;
readonly " $fragmentSpreads": FragmentRefs<"LinkedMeasuresCardFragment">;
};
}>;
};
readonly name: string;
readonly sectionTitle: string;
readonly " $fragmentSpreads": FragmentRefs<"FrameworkControlDialogFragment">;
};
}>;
};
@@ -53,97 +33,34 @@ export type FrameworkDetailPageFragment$key = {
const node: ReaderFragment = (function(){
var v0 = {
"count": null,
"cursor": null,
"direction": "forward",
"path": null
},
v1 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v2 = {
v1 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
v3 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "description",
"storageKey": null
},
v4 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
},
v5 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "cursor",
"storageKey": null
},
v6 = {
"alias": null,
"args": null,
"concreteType": "PageInfo",
"kind": "LinkedField",
"name": "pageInfo",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "endCursor",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasNextPage",
"storageKey": null
}
],
"storageKey": null
},
v7 = {
"kind": "ClientExtension",
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__id",
"storageKey": null
}
]
};
return {
"argumentDefinitions": [],
"kind": "Fragment",
"metadata": {
"connection": [
(v0/*: any*/),
(v0/*: any*/)
]
},
"metadata": null,
"name": "FrameworkDetailPageFragment",
"selections": [
(v0/*: any*/),
(v1/*: any*/),
(v2/*: any*/),
(v3/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "description",
"storageKey": null
},
{
"alias": null,
"args": [
@@ -174,7 +91,7 @@ return {
"name": "node",
"plural": false,
"selections": [
(v1/*: any*/),
(v0/*: any*/),
{
"alias": null,
"args": null,
@@ -182,106 +99,25 @@ return {
"name": "sectionTitle",
"storageKey": null
},
(v2/*: any*/),
(v3/*: any*/),
{
"args": null,
"kind": "FragmentSpread",
"name": "FrameworkControlDialogFragment"
},
{
"alias": "measures",
"args": null,
"concreteType": "MeasureConnection",
"kind": "LinkedField",
"name": "__FrameworkDetailPage_measures_connection",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "MeasureEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Measure",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v1/*: any*/),
{
"args": null,
"kind": "FragmentSpread",
"name": "LinkedMeasuresCardFragment"
},
(v4/*: any*/)
],
"storageKey": null
},
(v5/*: any*/)
],
"storageKey": null
},
(v6/*: any*/),
(v7/*: any*/)
],
"storageKey": null
},
{
"alias": "documents",
"args": null,
"concreteType": "DocumentConnection",
"kind": "LinkedField",
"name": "__FrameworkDetailPage_documents_connection",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "DocumentEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Document",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v1/*: any*/),
{
"args": null,
"kind": "FragmentSpread",
"name": "LinkedDocumentsCardFragment"
},
(v4/*: any*/)
],
"storageKey": null
},
(v5/*: any*/)
],
"storageKey": null
},
(v6/*: any*/),
(v7/*: any*/)
],
"storageKey": null
}
(v1/*: any*/)
],
"storageKey": null
}
],
"storageKey": null
},
(v7/*: any*/)
{
"kind": "ClientExtension",
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__id",
"storageKey": null
}
]
}
],
"storageKey": "controls(first:100)"
}
@@ -291,6 +127,6 @@ return {
};
})();
(node as any).hash = "b70c5b2090c5db158c44f0b9fcc0f9bc";
(node as any).hash = "e95f82575c72a8b95c87ecd3ffc676ed";
export default node;

View File

@@ -165,7 +165,11 @@ function routeTransformer({
useCleanup(dispose, 1000);
return <route.Component queryRef={queryRef} />;
return (
<Suspense fallback={FallbackComponent ? <FallbackComponent /> : null}>
<route.Component queryRef={queryRef} />
</Suspense>
);
},
};
}

View File

@@ -7,6 +7,7 @@ import { documentNodeQuery } from "/hooks/graph/DocumentGraph";
import { PageSkeleton } from "/components/skeletons/PageSkeleton";
import { redirect } from "react-router";
import { lazy } from "@probo/react-lazy";
import { LinkCardSkeleton } from "/components/skeletons/LinkCardSkeleton";
export const documentsRoutes = [
{
@@ -38,6 +39,7 @@ export const documentsRoutes = [
},
{
path: "description",
fallback: LinkCardSkeleton,
Component: lazy(
() =>
import(
@@ -47,6 +49,7 @@ export const documentsRoutes = [
},
{
path: "controls",
fallback: LinkCardSkeleton,
Component: lazy(
() =>
import("../pages/organizations/documents/tabs/DocumentControlsTab")

View File

@@ -4,10 +4,12 @@ import { relayEnvironment } from "/providers/RelayProviders";
import {
frameworksQuery,
frameworkNodeQuery,
frameworkControlNodeQuery,
} from "/hooks/graph/FrameworkGraph";
import type { AppRoute } from "/routes";
import { Fragment } from "react";
import { lazy } from "@probo/react-lazy";
import { ControlSkeleton } from "../components/skeletons/ControlSkeleton";
export const frameworkRoutes = [
{
@@ -34,7 +36,12 @@ export const frameworkRoutes = [
},
{
path: "controls/:controlId",
Component: Fragment,
fallback: ControlSkeleton,
queryLoader: ({ controlId }) =>
loadQuery(relayEnvironment, frameworkControlNodeQuery, { controlId }),
Component: lazy(
() => import("/pages/organizations/frameworks/FrameworkControlPage")
),
},
],
},

View File

@@ -6,6 +6,7 @@ import { measureNodeQuery, measuresQuery } from "/hooks/graph/MeasureGraph";
import { PageSkeleton } from "/components/skeletons/PageSkeleton";
import { redirect } from "react-router";
import { lazy } from "@probo/react-lazy";
import { LinkCardSkeleton } from "/components/skeletons/LinkCardSkeleton";
export const measureRoutes = [
{
@@ -39,18 +40,21 @@ export const measureRoutes = [
},
{
path: "risks",
fallback: LinkCardSkeleton,
Component: lazy(
() => import("/pages/organizations/measures/tabs/MeasureRisksTab.tsx")
),
},
{
path: "tasks",
fallback: LinkCardSkeleton,
Component: lazy(
() => import("/pages/organizations/measures/tabs/MeasureTasksTab.tsx")
),
},
{
path: "controls",
fallback: LinkCardSkeleton,
Component: lazy(
() =>
import("/pages/organizations/measures/tabs/MeasureControlsTab.tsx")
@@ -58,6 +62,7 @@ export const measureRoutes = [
},
{
path: "evidences",
fallback: LinkCardSkeleton,
Component: lazy(
() =>
import("/pages/organizations/measures/tabs/MeasureEvidencesTab.tsx")

View File

@@ -7,6 +7,7 @@ import {
paginatedPeopleQuery,
peopleNodeQuery,
} from "/hooks/graph/PeopleGraph";
import { LinkCardSkeleton } from "/components/skeletons/LinkCardSkeleton";
export const peopleRoutes = [
{
@@ -27,18 +28,21 @@ export const peopleRoutes = [
children: [
{
path: "tasks",
fallback: LinkCardSkeleton,
Component: lazy(
() => import("/pages/organizations/people/tabs/PeopleTasksTab")
),
},
{
path: "role",
fallback: LinkCardSkeleton,
Component: lazy(
() => import("/pages/organizations/people/tabs/PeopleRoleTab")
),
},
{
path: "profile",
fallback: LinkCardSkeleton,
Component: lazy(
() => import("/pages/organizations/people/tabs/PeopleProfileTab")
),

View File

@@ -7,6 +7,7 @@ import { riskNodeQuery, risksQuery } from "/hooks/graph/RiskGraph";
import { PageSkeleton } from "/components/skeletons/PageSkeleton";
import { redirect } from "react-router";
import { lazy } from "@probo/react-lazy";
import { LinkCardSkeleton } from "/components/skeletons/LinkCardSkeleton";
export const riskRoutes = [
{
@@ -32,24 +33,28 @@ export const riskRoutes = [
},
{
path: "overview",
fallback: LinkCardSkeleton,
Component: lazy(
() => import("/pages/organizations/risks/tabs/RiskOverviewTab.tsx")
),
},
{
path: "measures",
fallback: LinkCardSkeleton,
Component: lazy(
() => import("/pages/organizations/risks/tabs/RiskMeasuresTab.tsx")
),
},
{
path: "documents",
fallback: LinkCardSkeleton,
Component: lazy(
() => import("/pages/organizations/risks/tabs/RiskDocumentsTab.tsx")
),
},
{
path: "controls",
fallback: LinkCardSkeleton,
Component: lazy(
() => import("/pages/organizations/risks/tabs/RiskControlsTab.tsx")
),

View File

@@ -4,6 +4,7 @@ import type { AppRoute } from "/routes.tsx";
import { relayEnvironment } from "/providers/RelayProviders";
import { PageSkeleton } from "/components/skeletons/PageSkeleton";
import { vendorNodeQuery, vendorsQuery } from "/hooks/graph/VendorGraph";
import { LinkCardSkeleton } from "/components/skeletons/LinkCardSkeleton";
export const vendorRoutes = [
{
@@ -27,12 +28,14 @@ export const vendorRoutes = [
children: [
{
path: "overview",
fallback: LinkCardSkeleton,
Component: lazy(
() => import("../pages/organizations/vendors/tabs/VendorOverviewTab")
),
},
{
path: "certifications",
fallback: LinkCardSkeleton,
Component: lazy(
() =>
import(
@@ -42,6 +45,7 @@ export const vendorRoutes = [
},
{
path: "compliance",
fallback: LinkCardSkeleton,
Component: lazy(
() =>
import("../pages/organizations/vendors/tabs/VendorComplianceTab")
@@ -49,6 +53,7 @@ export const vendorRoutes = [
},
{
path: "risks",
fallback: LinkCardSkeleton,
Component: lazy(
() =>
import(