Add detail page

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Jonathan
2025-05-22 18:27:00 +02:00
committed by Sacha Al Himdani
parent 72c3fe586a
commit fe3128da2a
34 changed files with 969 additions and 922 deletions

View File

@@ -1,180 +0,0 @@
/**
* @generated SignedSource<<45329c77cfacf0f653adac2e9172b371>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type UserSelectQuery$variables = {
organizationId: string;
};
export type UserSelectQuery$data = {
readonly organization: {
readonly users?: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly fullName: string;
readonly id: string;
};
}>;
};
};
};
export type UserSelectQuery = {
response: UserSelectQuery$data;
variables: UserSelectQuery$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "organizationId"
}
],
v1 = [
{
"kind": "Variable",
"name": "id",
"variableName": "organizationId"
}
],
v2 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v3 = {
"kind": "InlineFragment",
"selections": [
{
"alias": null,
"args": [
{
"kind": "Literal",
"name": "first",
"value": 100
},
{
"kind": "Literal",
"name": "orderBy",
"value": {
"direction": "ASC",
"field": "CREATED_AT"
}
}
],
"concreteType": "UserConnection",
"kind": "LinkedField",
"name": "users",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "UserEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "User",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "fullName",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "users(first:100,orderBy:{\"direction\":\"ASC\",\"field\":\"CREATED_AT\"})"
}
],
"type": "Organization",
"abstractKey": null
};
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "UserSelectQuery",
"selections": [
{
"alias": "organization",
"args": (v1/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v3/*: any*/)
],
"storageKey": null
}
],
"type": "Query",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "UserSelectQuery",
"selections": [
{
"alias": "organization",
"args": (v1/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
},
(v3/*: any*/),
(v2/*: any*/)
],
"storageKey": null
}
]
},
"params": {
"cacheID": "795bb4c1131f2cacedb1ab8faf9f10ed",
"id": null,
"metadata": {},
"name": "UserSelectQuery",
"operationKind": "query",
"text": "query UserSelectQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n users(first: 100, orderBy: {direction: ASC, field: CREATED_AT}) {\n edges {\n node {\n id\n fullName\n }\n }\n }\n }\n id\n }\n}\n"
}
};
})();
(node as any).hash = "f2a6754264e15c49c53a9ac9fa60264e";
export default node;

View File

@@ -0,0 +1,12 @@
import { PageHeader, Skeleton } from "@probo/ui";
export function PageSkeleton() {
return (
<div className="space-y-6">
<PageHeader title={<Skeleton className="w-40 h-8" />} />
<div>
<Skeleton style={{ aspectRatio: "1280/280" }} />
</div>
</div>
);
}

View File

@@ -8,19 +8,24 @@ import type { MutationParameters, GraphQLTaggedNode } from "relay-runtime";
* A decorated useMutation hook that emits toast notifications on success or error.
*/
export function useMutationWithToasts<T extends MutationParameters>(
query: GraphQLTaggedNode
query: GraphQLTaggedNode,
baseOptions?: {
successMessage?: string;
errorMessage?: string;
}
) {
const [mutate, isLoading] = useMutation<T>(query);
const { toast } = useToast();
const { __ } = useTranslate();
const mutateWithToast = useCallback(
(
options: UseMutationConfig<T> & {
queryOptions: UseMutationConfig<T> & {
onSuccess?: () => void;
successMessage: string;
successMessage?: string;
errorMessage?: string;
}
) => {
const options = { ...baseOptions, ...queryOptions };
mutate({
...options,
onCompleted: (_, error) => {
@@ -35,7 +40,8 @@ export function useMutationWithToasts<T extends MutationParameters>(
}
toast({
title: __("Success"),
description: options.successMessage,
description:
options.successMessage ?? __("Operation completed successfully"),
variant: "success",
});
options.onSuccess?.();

View File

@@ -3,3 +3,4 @@
@import "@probo/ui/src/theme.css";
@import "tw-animate-css";
@source "../../../packages/ui/src";
@source "../../../packages/helpers/src";

View File

@@ -0,0 +1,21 @@
import { graphql } from "relay-runtime";
import { useTranslate } from "@probo/i18n";
import { useMutationWithToasts } from "../hooks/useMutationWithToasts";
import type { RisksDeleteMutation } from "./__generated__/RisksDeleteMutation.graphql";
const deleteRiskMutation = graphql`
mutation RisksDeleteMutation($input: DeleteRiskInput!, $connections: [ID!]!) {
deleteRisk(input: $input) {
deletedRiskId @deleteEdge(connections: $connections)
}
}
`;
export function useDeleteRiskMutation() {
const { __ } = useTranslate();
return useMutationWithToasts<RisksDeleteMutation>(deleteRiskMutation, {
successMessage: __("Risk deleted successfully."),
errorMessage: __("Failed to delete risk. Please try again."),
});
}

View File

@@ -0,0 +1,132 @@
/**
* @generated SignedSource<<5102c0f9ea660855dc5db7c6b16fd64b>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type DeleteRiskInput = {
riskId: string;
};
export type RisksDeleteMutation$variables = {
connections: ReadonlyArray<string>;
input: DeleteRiskInput;
};
export type RisksDeleteMutation$data = {
readonly deleteRisk: {
readonly deletedRiskId: string;
};
};
export type RisksDeleteMutation = {
response: RisksDeleteMutation$data;
variables: RisksDeleteMutation$variables;
};
const node: ConcreteRequest = (function(){
var v0 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "connections"
},
v1 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "input"
},
v2 = [
{
"kind": "Variable",
"name": "input",
"variableName": "input"
}
],
v3 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "deletedRiskId",
"storageKey": null
};
return {
"fragment": {
"argumentDefinitions": [
(v0/*: any*/),
(v1/*: any*/)
],
"kind": "Fragment",
"metadata": null,
"name": "RisksDeleteMutation",
"selections": [
{
"alias": null,
"args": (v2/*: any*/),
"concreteType": "DeleteRiskPayload",
"kind": "LinkedField",
"name": "deleteRisk",
"plural": false,
"selections": [
(v3/*: any*/)
],
"storageKey": null
}
],
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": [
(v1/*: any*/),
(v0/*: any*/)
],
"kind": "Operation",
"name": "RisksDeleteMutation",
"selections": [
{
"alias": null,
"args": (v2/*: any*/),
"concreteType": "DeleteRiskPayload",
"kind": "LinkedField",
"name": "deleteRisk",
"plural": false,
"selections": [
(v3/*: any*/),
{
"alias": null,
"args": null,
"filters": null,
"handle": "deleteEdge",
"key": "",
"kind": "ScalarHandle",
"name": "deletedRiskId",
"handleArgs": [
{
"kind": "Variable",
"name": "connections",
"variableName": "connections"
}
]
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "f4273ec8d2cd76543169a6d1b7cd72b3",
"id": null,
"metadata": {},
"name": "RisksDeleteMutation",
"operationKind": "mutation",
"text": "mutation RisksDeleteMutation(\n $input: DeleteRiskInput!\n) {\n deleteRisk(input: $input) {\n deletedRiskId\n }\n}\n"
}
};
})();
(node as any).hash = "ad609ad0f9c749fe47f2bd0d382777bb";
export default node;

View File

@@ -1,150 +0,0 @@
/**
* @generated SignedSource<<282a94b76fe5e17bd40b45ced6445ef3>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type OrganizationSelectionPageQuery$variables = Record<PropertyKey, never>;
export type OrganizationSelectionPageQuery$data = {
readonly viewer: {
readonly organizations: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly id: string;
readonly logoUrl: string | null | undefined;
readonly name: string;
};
}>;
};
};
};
export type OrganizationSelectionPageQuery = {
response: OrganizationSelectionPageQuery$data;
variables: OrganizationSelectionPageQuery$variables;
};
const node: ConcreteRequest = (function(){
var v0 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v1 = {
"alias": null,
"args": [
{
"kind": "Literal",
"name": "first",
"value": 25
}
],
"concreteType": "OrganizationConnection",
"kind": "LinkedField",
"name": "organizations",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "OrganizationEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Organization",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v0/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "logoUrl",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "organizations(first:25)"
};
return {
"fragment": {
"argumentDefinitions": [],
"kind": "Fragment",
"metadata": null,
"name": "OrganizationSelectionPageQuery",
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Viewer",
"kind": "LinkedField",
"name": "viewer",
"plural": false,
"selections": [
(v1/*: any*/)
],
"storageKey": null
}
],
"type": "Query",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": [],
"kind": "Operation",
"name": "OrganizationSelectionPageQuery",
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Viewer",
"kind": "LinkedField",
"name": "viewer",
"plural": false,
"selections": [
(v1/*: any*/),
(v0/*: any*/)
],
"storageKey": null
}
]
},
"params": {
"cacheID": "2938cb693a84c22366862de424a84d4b",
"id": null,
"metadata": {},
"name": "OrganizationSelectionPageQuery",
"operationKind": "query",
"text": "query OrganizationSelectionPageQuery {\n viewer {\n organizations(first: 25) {\n edges {\n node {\n id\n name\n logoUrl\n }\n }\n }\n id\n }\n}\n"
}
};
})();
(node as any).hash = "c41774fd198ab5f0f4b866d805099baf";
export default node;

View File

@@ -72,7 +72,12 @@ const updateRiskMutation = graphql`
/**
* Dialog to create or update a risk
*/
export default function FormRiskDialog({ trigger, risk, onSuccess }: Props) {
export default function FormRiskDialog({
trigger,
risk,
onSuccess,
open,
}: Props) {
const { __ } = useTranslate();
const organizationId = useOrganizationId();
@@ -131,11 +136,11 @@ export default function FormRiskDialog({ trigger, risk, onSuccess }: Props) {
});
const [showNote, toggleNote] = useToggle(false);
const [open, setOpen] = useState(!!risk);
const [isOpen, setOpen] = useState(open);
return (
<Dialog
open={open}
open={isOpen}
onOpenChange={setOpen}
trigger={trigger}
title={

View File

@@ -0,0 +1,153 @@
import {
ActionDropdown,
Avatar,
Breadcrumb,
Button,
ConfirmDialog,
Drawer,
DropdownItem,
IconPencil,
IconTrashCan,
PageHeader,
PropertyRow,
RiskOverview,
Badge,
} from "@probo/ui";
import { useNavigate, useParams } from "react-router";
import { useTranslate } from "@probo/i18n";
import { getTreatment, sprintf } from "@probo/helpers";
import { ConnectionHandler, graphql } from "relay-runtime";
import { useLazyLoadQuery } from "react-relay";
import type {
RiskDetailPageQuery,
RiskDetailPageQuery$data,
} from "./__generated__/RiskDetailPageQuery.graphql";
import FormRiskDialog from "./FormRiskDialog";
import { usePageTitle } from "@probo/hooks";
import { useOrganizationId } from "../../../hooks/useOrganizationId";
import { useDeleteRiskMutation } from "../../../mutations/Risks";
const riskQuery = graphql`
query RiskDetailPageQuery($riskId: ID!) {
node(id: $riskId) {
... on Risk {
name
description
treatment
owner {
id
fullName
}
inherentLikelihood
inherentImpact
residualLikelihood
residualImpact
note
createdAt
updatedAt
...useRiskFormFragment
}
}
}
`;
export default function RiskDetailPage() {
const { riskId } = useParams<{ riskId: string }>();
const organizationId = useOrganizationId();
const navigate = useNavigate();
if (!riskId) {
throw new Error("Cannot load risk detail page without riskId parameter");
}
const { __ } = useTranslate();
const data = useLazyLoadQuery<RiskDetailPageQuery>(riskQuery, { riskId });
const risk = data.node as Required<RiskDetailPageQuery$data["node"]>;
const [deleteRisk] = useDeleteRiskMutation();
usePageTitle(risk.name ?? "Risk detail");
const onDelete = (riskId: string) => {
const connectionId = ConnectionHandler.getConnectionID(
organizationId,
"RisksPage_risks"
);
deleteRisk({
variables: {
input: { riskId },
connections: [connectionId],
},
onSuccess() {
navigate(`/organizations/${organizationId}/risks`);
},
});
};
return (
<div className="space-y-6">
{/* Header */}
<div className="flex justify-between items-center mb-4">
<Breadcrumb
items={[
{
label: __("Risks"),
to: `/organizations/${organizationId}/risks`,
},
{
label: __("Risk detail"),
},
]}
/>
<div className="flex gap-2">
<FormRiskDialog
trigger={
<Button icon={IconPencil} variant="secondary">
{__("Edit")}
</Button>
}
risk={{ id: riskId, ...risk }}
/>
<ActionDropdown variant="secondary">
<ConfirmDialog
message={sprintf(
__(
'This will permanently delete the risk "%s". This action cannot be undone.'
),
risk.name
)}
onConfirm={() => onDelete(riskId)}
>
<DropdownItem variant="danger" icon={IconTrashCan}>
{__("Delete")}
</DropdownItem>
</ConfirmDialog>
</ActionDropdown>
</div>
</div>
<PageHeader title={risk.name} />
<div className="grid grid-cols-2">
<RiskOverview type="inherent" risk={risk} />
<RiskOverview type="residual" risk={risk} />
</div>
<Drawer>
<PropertyRow label={__("Owner")}>
<Badge variant="highlight" size="md" className="gap-2">
<Avatar name={risk.owner?.fullName ?? ""} />
{risk.owner?.fullName}
</Badge>
</PropertyRow>
<PropertyRow label={__("Treatment")}>
<Badge variant="highlight" size="md" className="gap-2">
{getTreatment(__, risk.treatment)}
</Badge>
</PropertyRow>
<PropertyRow label={__("Note")}>
<div className="text-sm text-txt-secondary">{risk.note}</div>
</PropertyRow>
</Drawer>
</div>
);
}

View File

@@ -30,8 +30,9 @@ import { useMemo, useState } from "react";
import { usePageTitle } from "@probo/hooks";
import type { RisksPageDeleteMutation } from "./__generated__/RisksPageDeleteMutation.graphql";
import { useMutationWithToasts } from "../../../hooks/useMutationWithToasts";
import { sprintf } from "@probo/helpers";
import { getTreatment, sprintf } from "@probo/helpers";
import type { ItemOf } from "../../../types";
import { useDeleteRiskMutation } from "../../../mutations/Risks";
const risksQuery = graphql`
query RisksPageQuery(
@@ -65,17 +66,6 @@ const risksQuery = graphql`
}
`;
const deleteRiskMutation = graphql`
mutation RisksPageDeleteMutation(
$input: DeleteRiskInput!
$connections: [ID!]!
) {
deleteRisk(input: $input) {
deletedRiskId @deleteEdge(connections: $connections)
}
}
`;
export default function RisksPage() {
const { __ } = useTranslate();
const data = useLazyLoadQuery<RisksPageQueryType>(risksQuery, {
@@ -85,18 +75,9 @@ export default function RisksPage() {
const [editedRisk, setEditedRisk] = useState<ItemOf<typeof risks> | null>(
null
);
const treatmentLabels = useMemo(() => {
return {
MITIGATED: __("Mitigate"),
ACCEPTED: __("Accept"),
TRANSFERRED: __("Transfer"),
AVOIDED: __("Avoid"),
} as Record<RiskTreatment, string>;
}, [__]);
const organizationId = useOrganizationId();
const [deleteRisk] =
useMutationWithToasts<RisksPageDeleteMutation>(deleteRiskMutation);
const [deleteRisk] = useDeleteRiskMutation();
const onDelete = (riskId: string) => {
const connectionId = ConnectionHandler.getConnectionID(
@@ -108,8 +89,6 @@ export default function RisksPage() {
input: { riskId },
connections: [connectionId],
},
successMessage: __("Risk deleted successfully."),
errorMessage: __("Failed to delete risk. Please try again."),
});
};
@@ -124,6 +103,7 @@ export default function RisksPage() {
</PageHeader>
{editedRisk && (
<FormRiskDialog
open
risk={editedRisk}
onSuccess={() => setEditedRisk(null)}
/>
@@ -161,7 +141,7 @@ export default function RisksPage() {
>
<Td>{risk.name}</Td>
<Td>{risk.category}</Td>
<Td>{treatmentLabels[risk.treatment]}</Td>
<Td>{getTreatment(__, risk.treatment)}</Td>
<Td>
<RiskBadge
score={risk.inherentLikelihood * risk.inherentImpact}

View File

@@ -1,249 +0,0 @@
/**
* @generated SignedSource<<3577f9a5c1b1f5b26dcac244a9a55fc5>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type RiskTreatment = "ACCEPTED" | "AVOIDED" | "MITIGATED" | "TRANSFERRED" | "%future added value";
export type CreateRiskInput = {
category: string;
description: string;
inherentImpact: number;
inherentLikelihood: number;
name: string;
note?: string | null | undefined;
organizationId: string;
ownerId?: string | null | undefined;
residualImpact?: number | null | undefined;
residualLikelihood?: number | null | undefined;
treatment: RiskTreatment;
};
export type NewRiskDialogMutation$variables = {
connections: ReadonlyArray<string>;
input: CreateRiskInput;
};
export type NewRiskDialogMutation$data = {
readonly createRisk: {
readonly riskEdge: {
readonly node: {
readonly category: string;
readonly createdAt: any;
readonly description: string;
readonly id: string;
readonly inherentImpact: number;
readonly inherentLikelihood: number;
readonly name: string;
readonly residualImpact: number;
readonly residualLikelihood: number;
readonly treatment: RiskTreatment;
readonly updatedAt: any;
};
};
};
};
export type NewRiskDialogMutation = {
response: NewRiskDialogMutation$data;
variables: NewRiskDialogMutation$variables;
};
const node: ConcreteRequest = (function(){
var v0 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "connections"
},
v1 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "input"
},
v2 = [
{
"kind": "Variable",
"name": "input",
"variableName": "input"
}
],
v3 = {
"alias": null,
"args": null,
"concreteType": "RiskEdge",
"kind": "LinkedField",
"name": "riskEdge",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Risk",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "description",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "category",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "inherentLikelihood",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "inherentImpact",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "residualLikelihood",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "residualImpact",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "treatment",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "updatedAt",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
};
return {
"fragment": {
"argumentDefinitions": [
(v0/*: any*/),
(v1/*: any*/)
],
"kind": "Fragment",
"metadata": null,
"name": "NewRiskDialogMutation",
"selections": [
{
"alias": null,
"args": (v2/*: any*/),
"concreteType": "CreateRiskPayload",
"kind": "LinkedField",
"name": "createRisk",
"plural": false,
"selections": [
(v3/*: any*/)
],
"storageKey": null
}
],
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": [
(v1/*: any*/),
(v0/*: any*/)
],
"kind": "Operation",
"name": "NewRiskDialogMutation",
"selections": [
{
"alias": null,
"args": (v2/*: any*/),
"concreteType": "CreateRiskPayload",
"kind": "LinkedField",
"name": "createRisk",
"plural": false,
"selections": [
(v3/*: any*/),
{
"alias": null,
"args": null,
"filters": null,
"handle": "prependEdge",
"key": "",
"kind": "LinkedHandle",
"name": "riskEdge",
"handleArgs": [
{
"kind": "Variable",
"name": "connections",
"variableName": "connections"
}
]
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "e9b461ea2a98edd7d4a1b289d5d4673f",
"id": null,
"metadata": {},
"name": "NewRiskDialogMutation",
"operationKind": "mutation",
"text": "mutation NewRiskDialogMutation(\n $input: CreateRiskInput!\n) {\n createRisk(input: $input) {\n riskEdge {\n node {\n id\n name\n description\n category\n inherentLikelihood\n inherentImpact\n residualLikelihood\n residualImpact\n treatment\n createdAt\n updatedAt\n }\n }\n }\n}\n"
}
};
})();
(node as any).hash = "532223f85627f715a9a457680073f358";
export default node;

View File

@@ -0,0 +1,262 @@
/**
* @generated SignedSource<<cfb39c3c814ed520de659842d6fffe38>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type RiskTreatment = "ACCEPTED" | "AVOIDED" | "MITIGATED" | "TRANSFERRED" | "%future added value";
export type RiskDetailPageQuery$variables = {
riskId: string;
};
export type RiskDetailPageQuery$data = {
readonly node: {
readonly createdAt?: any;
readonly description?: string;
readonly inherentImpact?: number;
readonly inherentLikelihood?: number;
readonly name?: string;
readonly note?: string;
readonly owner?: {
readonly fullName: string;
readonly id: string;
} | null | undefined;
readonly residualImpact?: number;
readonly residualLikelihood?: number;
readonly treatment?: RiskTreatment;
readonly updatedAt?: any;
readonly " $fragmentSpreads": FragmentRefs<"useRiskFormFragment">;
};
};
export type RiskDetailPageQuery = {
response: RiskDetailPageQuery$data;
variables: RiskDetailPageQuery$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "riskId"
}
],
v1 = [
{
"kind": "Variable",
"name": "id",
"variableName": "riskId"
}
],
v2 = {
"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": "treatment",
"storageKey": null
},
v5 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v6 = {
"alias": null,
"args": null,
"concreteType": "People",
"kind": "LinkedField",
"name": "owner",
"plural": false,
"selections": [
(v5/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "fullName",
"storageKey": null
}
],
"storageKey": null
},
v7 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "inherentLikelihood",
"storageKey": null
},
v8 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "inherentImpact",
"storageKey": null
},
v9 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "residualLikelihood",
"storageKey": null
},
v10 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "residualImpact",
"storageKey": null
},
v11 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "note",
"storageKey": null
},
v12 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
},
v13 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "updatedAt",
"storageKey": null
};
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "RiskDetailPageQuery",
"selections": [
{
"alias": null,
"args": (v1/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"kind": "InlineFragment",
"selections": [
(v2/*: any*/),
(v3/*: any*/),
(v4/*: any*/),
(v6/*: any*/),
(v7/*: any*/),
(v8/*: any*/),
(v9/*: any*/),
(v10/*: any*/),
(v11/*: any*/),
(v12/*: any*/),
(v13/*: any*/),
{
"args": null,
"kind": "FragmentSpread",
"name": "useRiskFormFragment"
}
],
"type": "Risk",
"abstractKey": null
}
],
"storageKey": null
}
],
"type": "Query",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "RiskDetailPageQuery",
"selections": [
{
"alias": null,
"args": (v1/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
},
(v5/*: any*/),
{
"kind": "InlineFragment",
"selections": [
(v2/*: any*/),
(v3/*: any*/),
(v4/*: any*/),
(v6/*: any*/),
(v7/*: any*/),
(v8/*: any*/),
(v9/*: any*/),
(v10/*: any*/),
(v11/*: any*/),
(v12/*: any*/),
(v13/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "category",
"storageKey": null
}
],
"type": "Risk",
"abstractKey": null
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "0ed24be13c829da91705d506a46eb562",
"id": null,
"metadata": {},
"name": "RiskDetailPageQuery",
"operationKind": "query",
"text": "query RiskDetailPageQuery(\n $riskId: ID!\n) {\n node(id: $riskId) {\n __typename\n ... on Risk {\n name\n description\n treatment\n owner {\n id\n fullName\n }\n inherentLikelihood\n inherentImpact\n residualLikelihood\n residualImpact\n note\n createdAt\n updatedAt\n ...useRiskFormFragment\n }\n id\n }\n}\n\nfragment useRiskFormFragment on Risk {\n id\n name\n category\n description\n treatment\n inherentLikelihood\n inherentImpact\n residualLikelihood\n residualImpact\n note\n owner {\n id\n }\n}\n"
}
};
})();
(node as any).hash = "94de027c557f9b633765f9dde7c0aae1";
export default node;

View File

@@ -1,220 +0,0 @@
/**
* @generated SignedSource<<78e5a6f1db510ffe28be99140539b234>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type RiskTreatment = "ACCEPTED" | "AVOIDED" | "MITIGATED" | "TRANSFERRED" | "%future added value";
export type UpdateRiskInput = {
category?: string | null | undefined;
description?: string | null | undefined;
id: string;
inherentImpact?: number | null | undefined;
inherentLikelihood?: number | null | undefined;
name?: string | null | undefined;
note?: string | null | undefined;
ownerId?: string | null | undefined;
residualImpact?: number | null | undefined;
residualLikelihood?: number | null | undefined;
treatment?: RiskTreatment | null | undefined;
};
export type RisksPageUpdateRiskMutation$variables = {
input: UpdateRiskInput;
};
export type RisksPageUpdateRiskMutation$data = {
readonly updateRisk: {
readonly risk: {
readonly category: string;
readonly description: string;
readonly id: string;
readonly inherentImpact: number;
readonly inherentLikelihood: number;
readonly name: string;
readonly note: string;
readonly owner: {
readonly fullName: string;
readonly id: string;
} | null | undefined;
readonly residualImpact: number;
readonly residualLikelihood: number;
readonly treatment: RiskTreatment;
readonly updatedAt: any;
};
};
};
export type RisksPageUpdateRiskMutation = {
response: RisksPageUpdateRiskMutation$data;
variables: RisksPageUpdateRiskMutation$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "input"
}
],
v1 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v2 = [
{
"alias": null,
"args": [
{
"kind": "Variable",
"name": "input",
"variableName": "input"
}
],
"concreteType": "UpdateRiskPayload",
"kind": "LinkedField",
"name": "updateRisk",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Risk",
"kind": "LinkedField",
"name": "risk",
"plural": false,
"selections": [
(v1/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "description",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "category",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "inherentLikelihood",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "inherentImpact",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "residualLikelihood",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "residualImpact",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "treatment",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "note",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "updatedAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "People",
"kind": "LinkedField",
"name": "owner",
"plural": false,
"selections": [
(v1/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "fullName",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "RisksPageUpdateRiskMutation",
"selections": (v2/*: any*/),
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "RisksPageUpdateRiskMutation",
"selections": (v2/*: any*/)
},
"params": {
"cacheID": "a3c421bf079967f96acf2215e72cf8a7",
"id": null,
"metadata": {},
"name": "RisksPageUpdateRiskMutation",
"operationKind": "mutation",
"text": "mutation RisksPageUpdateRiskMutation(\n $input: UpdateRiskInput!\n) {\n updateRisk(input: $input) {\n risk {\n id\n name\n description\n category\n inherentLikelihood\n inherentImpact\n residualLikelihood\n residualImpact\n treatment\n note\n updatedAt\n owner {\n id\n fullName\n }\n }\n }\n}\n"
}
};
})();
(node as any).hash = "e3c8daa449e937742695adf8deb6dbe5";
export default node;

View File

@@ -7,7 +7,8 @@ import { MainLayout } from "./layouts/MainLayout";
import { AuthLayout, CenteredLayout, CenteredLayoutSkeleton } from "@probo/ui";
import { lazy, Suspense, type FC } from "react";
import { UnAuthenticatedError } from "./providers/RelayProviders";
import { RisksPageSkeleton } from "./pages/organizations/risks/skeletons/RisksPage";
import { RisksPageSkeleton } from "./components/skeletons/RisksPageSkeleton.tsx";
import { PageSkeleton } from "./components/skeletons/PageSkeleton.tsx";
function ErrorBoundary() {
const error = useRouteError();
@@ -49,7 +50,7 @@ const routes = [
{
path: "organizations/new",
Component: lazy(
() => import("./pages/organizations/NewOrganizationPage")
() => import("./pages/organizations/NewOrganizationPage"),
),
},
],
@@ -64,6 +65,13 @@ const routes = [
fallback: RisksPageSkeleton,
Component: lazy(() => import("./pages/organizations/risks/RisksPage")),
},
{
path: "risks/:riskId",
fallback: PageSkeleton,
Component: lazy(
() => import("./pages/organizations/risks/RiskDetailPage"),
),
},
],
},
] satisfies Route[];
@@ -74,7 +82,6 @@ const routes = [
function routeTransformer(route: Route): RouteObject {
if ("fallback" in route && route.fallback) {
const fallback = <route.fallback />;
console.log(fallback, route.path);
return {
...route,
children: route.children?.map(routeTransformer),