From b660c0ce7f5f8cec35e651fc8b049428b51856d4 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Tue, 3 Jun 2025 18:35:21 +0200 Subject: [PATCH] Add risk assessment tab Signed-off-by: Bryan Frimin Signed-off-by: Sacha Al Himdani --- ...iewCreateRiskAssessmentMutation.graphql.ts | 350 ++++++++-------- apps/console2/src/hooks/graph/VendorGraph.ts | 10 +- .../VendorGraphNodeQuery.graphql.ts | 374 ++++++++++++++---- .../organizations/measures/MeasuresPage.tsx | 9 +- .../organizations/people/PeopleListPage.tsx | 1 - .../vendors/VendorDetailPage.tsx | 7 +- .../organizations/vendors/VendorsPage.tsx | 4 +- .../dialogs/CreateRiskAssessmentDialog.tsx | 143 +++++++ .../{ => dialogs}/CreateVendorDialog.tsx | 0 ...ateRiskAssessmentDialogMutation.graphql.ts | 246 ++++++++++++ .../vendors/tabs/VendorRiskAssessmentTab.tsx | 205 ++++++++++ ...VendorRiskAssessmentTabFragment.graphql.ts | 229 +++++++++++ ...ssessmentTabFragment_assessment.graphql.ts | 110 ++++++ .../VendorRiskAssessmentTabQuery.graphql.ts | 370 +++++++++++++++++ apps/console2/src/routes/vendorRoutes.ts | 12 +- packages/i18n/TranslatorProvider.tsx | 42 +- packages/i18n/package.json | 19 +- packages/ui/src/Atoms/Button/Button.tsx | 2 +- packages/ui/src/Atoms/Dropzone/Dropzone.tsx | 2 - packages/ui/src/Atoms/Select/Select.tsx | 15 +- packages/ui/src/Atoms/Table/Table.tsx | 38 +- packages/ui/src/Foundation.stories.tsx | 1 - .../Select/ImpactOptions.stories.tsx | 21 + .../ui/src/Molecules/Select/ImpactOptions.tsx | 47 +++ .../Select/SentitivityOptions.stories.tsx | 21 + .../Molecules/Select/SentitivityOptions.tsx | 51 +++ packages/ui/src/index.ts | 2 + 27 files changed, 2020 insertions(+), 311 deletions(-) create mode 100644 apps/console2/src/pages/organizations/vendors/dialogs/CreateRiskAssessmentDialog.tsx rename apps/console2/src/pages/organizations/vendors/{ => dialogs}/CreateVendorDialog.tsx (100%) create mode 100644 apps/console2/src/pages/organizations/vendors/dialogs/__generated__/CreateRiskAssessmentDialogMutation.graphql.ts create mode 100644 apps/console2/src/pages/organizations/vendors/tabs/VendorRiskAssessmentTab.tsx create mode 100644 apps/console2/src/pages/organizations/vendors/tabs/__generated__/VendorRiskAssessmentTabFragment.graphql.ts create mode 100644 apps/console2/src/pages/organizations/vendors/tabs/__generated__/VendorRiskAssessmentTabFragment_assessment.graphql.ts create mode 100644 apps/console2/src/pages/organizations/vendors/tabs/__generated__/VendorRiskAssessmentTabQuery.graphql.ts create mode 100644 packages/ui/src/Molecules/Select/ImpactOptions.stories.tsx create mode 100644 packages/ui/src/Molecules/Select/ImpactOptions.tsx create mode 100644 packages/ui/src/Molecules/Select/SentitivityOptions.stories.tsx create mode 100644 packages/ui/src/Molecules/Select/SentitivityOptions.tsx diff --git a/apps/console/src/pages/organizations/vendors/__generated__/VendorViewCreateRiskAssessmentMutation.graphql.ts b/apps/console/src/pages/organizations/vendors/__generated__/VendorViewCreateRiskAssessmentMutation.graphql.ts index cf084295d..dbf645a87 100644 --- a/apps/console/src/pages/organizations/vendors/__generated__/VendorViewCreateRiskAssessmentMutation.graphql.ts +++ b/apps/console/src/pages/organizations/vendors/__generated__/VendorViewCreateRiskAssessmentMutation.graphql.ts @@ -8,7 +8,7 @@ /* eslint-disable */ // @ts-nocheck -import { ConcreteRequest } from 'relay-runtime'; +import { ConcreteRequest } from "relay-runtime"; export type BusinessImpact = "CRITICAL" | "HIGH" | "LOW" | "MEDIUM"; export type DataSensitivity = "CRITICAL" | "HIGH" | "LOW" | "MEDIUM" | "NONE"; export type CreateVendorRiskAssessmentInput = { @@ -47,189 +47,181 @@ export type VendorViewCreateRiskAssessmentMutation = { variables: VendorViewCreateRiskAssessmentMutation$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": "id", - "storageKey": null -}, -v4 = { - "alias": null, - "args": null, - "concreteType": "VendorRiskAssessmentEdge", - "kind": "LinkedField", - "name": "vendorRiskAssessmentEdge", - "plural": false, - "selections": [ - { - "alias": null, - "args": null, - "concreteType": "VendorRiskAssessment", - "kind": "LinkedField", - "name": "node", - "plural": false, - "selections": [ - (v3/*: any*/), +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: "id", + storageKey: null, + }, + v4 = { + alias: null, + args: null, + concreteType: "VendorRiskAssessmentEdge", + kind: "LinkedField", + name: "vendorRiskAssessmentEdge", + plural: false, + selections: [ { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "assessedAt", - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "expiresAt", - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "dataSensitivity", - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "businessImpact", - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "notes", - "storageKey": null - }, - { - "alias": null, - "args": null, - "concreteType": "People", - "kind": "LinkedField", - "name": "assessedBy", - "plural": false, - "selections": [ - (v3/*: any*/), + alias: null, + args: null, + concreteType: "VendorRiskAssessment", + kind: "LinkedField", + name: "node", + plural: false, + selections: [ + v3 /*: any*/, { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "fullName", - "storageKey": null - } + alias: null, + args: null, + kind: "ScalarField", + name: "assessedAt", + storageKey: null, + }, + { + alias: null, + args: null, + kind: "ScalarField", + name: "expiresAt", + storageKey: null, + }, + { + alias: null, + args: null, + kind: "ScalarField", + name: "dataSensitivity", + storageKey: null, + }, + { + alias: null, + args: null, + kind: "ScalarField", + name: "businessImpact", + storageKey: null, + }, + { + alias: null, + args: null, + kind: "ScalarField", + name: "notes", + storageKey: null, + }, + { + alias: null, + args: null, + concreteType: "People", + kind: "LinkedField", + name: "assessedBy", + plural: false, + selections: [ + v3 /*: any*/, + { + alias: null, + args: null, + kind: "ScalarField", + name: "fullName", + storageKey: null, + }, + ], + storageKey: null, + }, + { + alias: null, + args: null, + kind: "ScalarField", + name: "createdAt", + storageKey: null, + }, ], - "storageKey": null + storageKey: null, }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "createdAt", - "storageKey": null - } ], - "storageKey": null - } - ], - "storageKey": null -}; -return { - "fragment": { - "argumentDefinitions": [ - (v0/*: any*/), - (v1/*: any*/) - ], - "kind": "Fragment", - "metadata": null, - "name": "VendorViewCreateRiskAssessmentMutation", - "selections": [ - { - "alias": null, - "args": (v2/*: any*/), - "concreteType": "CreateVendorRiskAssessmentPayload", - "kind": "LinkedField", - "name": "createVendorRiskAssessment", - "plural": false, - "selections": [ - (v4/*: any*/) - ], - "storageKey": null - } - ], - "type": "Mutation", - "abstractKey": null - }, - "kind": "Request", - "operation": { - "argumentDefinitions": [ - (v1/*: any*/), - (v0/*: any*/) - ], - "kind": "Operation", - "name": "VendorViewCreateRiskAssessmentMutation", - "selections": [ - { - "alias": null, - "args": (v2/*: any*/), - "concreteType": "CreateVendorRiskAssessmentPayload", - "kind": "LinkedField", - "name": "createVendorRiskAssessment", - "plural": false, - "selections": [ - (v4/*: any*/), - { - "alias": null, - "args": null, - "filters": null, - "handle": "prependEdge", - "key": "", - "kind": "LinkedHandle", - "name": "vendorRiskAssessmentEdge", - "handleArgs": [ - { - "kind": "Variable", - "name": "connections", - "variableName": "connections" - } - ] - } - ], - "storageKey": null - } - ] - }, - "params": { - "cacheID": "1238320b8db75088917d0e5796652f50", - "id": null, - "metadata": {}, - "name": "VendorViewCreateRiskAssessmentMutation", - "operationKind": "mutation", - "text": "mutation VendorViewCreateRiskAssessmentMutation(\n $input: CreateVendorRiskAssessmentInput!\n) {\n createVendorRiskAssessment(input: $input) {\n vendorRiskAssessmentEdge {\n node {\n id\n assessedAt\n expiresAt\n dataSensitivity\n businessImpact\n notes\n assessedBy {\n id\n fullName\n }\n createdAt\n }\n }\n }\n}\n" - } -}; + storageKey: null, + }; + return { + fragment: { + argumentDefinitions: [v0 /*: any*/, v1 /*: any*/], + kind: "Fragment", + metadata: null, + name: "VendorViewCreateRiskAssessmentMutation", + selections: [ + { + alias: null, + args: v2 /*: any*/, + concreteType: "CreateVendorRiskAssessmentPayload", + kind: "LinkedField", + name: "createVendorRiskAssessment", + plural: false, + selections: [v4 /*: any*/], + storageKey: null, + }, + ], + type: "Mutation", + abstractKey: null, + }, + kind: "Request", + operation: { + argumentDefinitions: [v1 /*: any*/, v0 /*: any*/], + kind: "Operation", + name: "VendorViewCreateRiskAssessmentMutation", + selections: [ + { + alias: null, + args: v2 /*: any*/, + concreteType: "CreateVendorRiskAssessmentPayload", + kind: "LinkedField", + name: "createVendorRiskAssessment", + plural: false, + selections: [ + v4 /*: any*/, + { + alias: null, + args: null, + filters: null, + handle: "prependEdge", + key: "", + kind: "LinkedHandle", + name: "vendorRiskAssessmentEdge", + handleArgs: [ + { + kind: "Variable", + name: "connections", + variableName: "connections", + }, + ], + }, + ], + storageKey: null, + }, + ], + }, + params: { + cacheID: "1238320b8db75088917d0e5796652f50", + id: null, + metadata: {}, + name: "VendorViewCreateRiskAssessmentMutation", + operationKind: "mutation", + text: "mutation VendorViewCreateRiskAssessmentMutation(\n $input: CreateVendorRiskAssessmentInput!\n) {\n createVendorRiskAssessment(input: $input) {\n vendorRiskAssessmentEdge {\n node {\n id\n assessedAt\n expiresAt\n dataSensitivity\n businessImpact\n notes\n assessedBy {\n id\n fullName\n }\n createdAt\n }\n }\n }\n}\n", + }, + }; })(); (node as any).hash = "d28e057eba5f7287f3d0f5e38d444ca5"; diff --git a/apps/console2/src/hooks/graph/VendorGraph.ts b/apps/console2/src/hooks/graph/VendorGraph.ts index b013b91fd..997191ce9 100644 --- a/apps/console2/src/hooks/graph/VendorGraph.ts +++ b/apps/console2/src/hooks/graph/VendorGraph.ts @@ -124,7 +124,7 @@ export const vendorsQuery = graphql` `; export const vendorNodeQuery = graphql` - query VendorGraphNodeQuery($vendorId: ID!) { + query VendorGraphNodeQuery($vendorId: ID!, $organizationId: ID!) { node(id: $vendorId) { ... on Vendor { id @@ -132,6 +132,14 @@ export const vendorNodeQuery = graphql` websiteUrl ...useVendorFormFragment ...VendorComplianceTabFragment + ...VendorRiskAssessmentTabFragment + } + } + viewer { + user { + people(organizationId: $organizationId) { + id + } } } } diff --git a/apps/console2/src/hooks/graph/__generated__/VendorGraphNodeQuery.graphql.ts b/apps/console2/src/hooks/graph/__generated__/VendorGraphNodeQuery.graphql.ts index 7ff554f66..43e6b5da3 100644 --- a/apps/console2/src/hooks/graph/__generated__/VendorGraphNodeQuery.graphql.ts +++ b/apps/console2/src/hooks/graph/__generated__/VendorGraphNodeQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<6d50678dccfbe35e4ade26b58cda2ad3>> + * @generated SignedSource<<2b4b64bb6df013e073b34e8249232ab2>> * @lightSyntaxTransform * @nogrep */ @@ -11,6 +11,7 @@ import { ConcreteRequest } from 'relay-runtime'; import { FragmentRefs } from "relay-runtime"; export type VendorGraphNodeQuery$variables = { + organizationId: string; vendorId: string; }; export type VendorGraphNodeQuery$data = { @@ -18,7 +19,14 @@ export type VendorGraphNodeQuery$data = { readonly id?: string; readonly name?: string; readonly websiteUrl?: string | null | undefined; - readonly " $fragmentSpreads": FragmentRefs<"VendorComplianceTabFragment" | "useVendorFormFragment">; + readonly " $fragmentSpreads": FragmentRefs<"VendorComplianceTabFragment" | "VendorRiskAssessmentTabFragment" | "useVendorFormFragment">; + }; + readonly viewer: { + readonly user: { + readonly people: { + readonly id: string; + } | null | undefined; + }; }; }; export type VendorGraphNodeQuery = { @@ -27,68 +35,140 @@ export type VendorGraphNodeQuery = { }; const node: ConcreteRequest = (function(){ -var v0 = [ - { - "defaultValue": null, - "kind": "LocalArgument", - "name": "vendorId" - } -], -v1 = [ +var v0 = { + "defaultValue": null, + "kind": "LocalArgument", + "name": "organizationId" +}, +v1 = { + "defaultValue": null, + "kind": "LocalArgument", + "name": "vendorId" +}, +v2 = [ { "kind": "Variable", "name": "id", "variableName": "vendorId" } ], -v2 = { +v3 = { "alias": null, "args": null, "kind": "ScalarField", "name": "id", "storageKey": null }, -v3 = { +v4 = { "alias": null, "args": null, "kind": "ScalarField", "name": "name", "storageKey": null }, -v4 = { +v5 = { "alias": null, "args": null, "kind": "ScalarField", "name": "websiteUrl", "storageKey": null }, -v5 = { +v6 = [ + (v3/*: any*/) +], +v7 = { + "alias": null, + "args": [ + { + "kind": "Variable", + "name": "organizationId", + "variableName": "organizationId" + } + ], + "concreteType": "People", + "kind": "LinkedField", + "name": "people", + "plural": false, + "selections": (v6/*: any*/), + "storageKey": null +}, +v8 = { "alias": null, "args": null, "kind": "ScalarField", "name": "__typename", "storageKey": null }, -v6 = [ - (v2/*: any*/) -], -v7 = [ +v9 = [ { "kind": "Literal", "name": "first", "value": 50 } +], +v10 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "cursor", + "storageKey": null +}, +v11 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "endCursor", + "storageKey": null +}, +v12 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "hasNextPage", + "storageKey": null +}, +v13 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "hasPreviousPage", + "storageKey": null +}, +v14 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "startCursor", + "storageKey": null +}, +v15 = { + "kind": "ClientExtension", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__id", + "storageKey": null + } + ] +}, +v16 = [ + "orderBy" ]; return { "fragment": { - "argumentDefinitions": (v0/*: any*/), + "argumentDefinitions": [ + (v0/*: any*/), + (v1/*: any*/) + ], "kind": "Fragment", "metadata": null, "name": "VendorGraphNodeQuery", "selections": [ { "alias": null, - "args": (v1/*: any*/), + "args": (v2/*: any*/), "concreteType": null, "kind": "LinkedField", "name": "node", @@ -97,9 +177,9 @@ return { { "kind": "InlineFragment", "selections": [ - (v2/*: any*/), (v3/*: any*/), (v4/*: any*/), + (v5/*: any*/), { "args": null, "kind": "FragmentSpread", @@ -109,6 +189,11 @@ return { "args": null, "kind": "FragmentSpread", "name": "VendorComplianceTabFragment" + }, + { + "args": null, + "kind": "FragmentSpread", + "name": "VendorRiskAssessmentTabFragment" } ], "type": "Vendor", @@ -116,6 +201,29 @@ return { } ], "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "Viewer", + "kind": "LinkedField", + "name": "viewer", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "User", + "kind": "LinkedField", + "name": "user", + "plural": false, + "selections": [ + (v7/*: any*/) + ], + "storageKey": null + } + ], + "storageKey": null } ], "type": "Query", @@ -123,25 +231,28 @@ return { }, "kind": "Request", "operation": { - "argumentDefinitions": (v0/*: any*/), + "argumentDefinitions": [ + (v1/*: any*/), + (v0/*: any*/) + ], "kind": "Operation", "name": "VendorGraphNodeQuery", "selections": [ { "alias": null, - "args": (v1/*: any*/), + "args": (v2/*: any*/), "concreteType": null, "kind": "LinkedField", "name": "node", "plural": false, "selections": [ - (v5/*: any*/), - (v2/*: any*/), + (v8/*: any*/), + (v3/*: any*/), { "kind": "InlineFragment", "selections": [ - (v3/*: any*/), (v4/*: any*/), + (v5/*: any*/), { "alias": null, "args": null, @@ -241,7 +352,7 @@ return { }, { "alias": null, - "args": (v7/*: any*/), + "args": (v9/*: any*/), "concreteType": "VendorComplianceReportConnection", "kind": "LinkedField", "name": "complianceReports", @@ -263,7 +374,7 @@ return { "name": "node", "plural": false, "selections": [ - (v2/*: any*/), + (v3/*: any*/), { "alias": null, "args": null, @@ -299,17 +410,11 @@ return { "name": "fileSize", "storageKey": null }, - (v5/*: any*/) + (v8/*: any*/) ], "storageKey": null }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "cursor", - "storageKey": null - } + (v10/*: any*/) ], "storageKey": null }, @@ -321,62 +426,140 @@ return { "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 - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "hasPreviousPage", - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "startCursor", - "storageKey": null - } + (v11/*: any*/), + (v12/*: any*/), + (v13/*: any*/), + (v14/*: any*/) ], "storageKey": null }, - { - "kind": "ClientExtension", - "selections": [ - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "__id", - "storageKey": null - } - ] - } + (v15/*: any*/) ], "storageKey": "complianceReports(first:50)" }, { "alias": null, - "args": (v7/*: any*/), - "filters": [ - "orderBy" - ], + "args": (v9/*: any*/), + "filters": (v16/*: any*/), "handle": "connection", "key": "VendorComplianceTabFragment_complianceReports", "kind": "LinkedHandle", "name": "complianceReports" + }, + { + "alias": null, + "args": (v9/*: any*/), + "concreteType": "VendorRiskAssessmentConnection", + "kind": "LinkedField", + "name": "riskAssessments", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "VendorRiskAssessmentEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "VendorRiskAssessment", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v3/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "assessedAt", + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "People", + "kind": "LinkedField", + "name": "assessedBy", + "plural": false, + "selections": [ + (v3/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "fullName", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "expiresAt", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "dataSensitivity", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "businessImpact", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "notes", + "storageKey": null + }, + (v8/*: any*/) + ], + "storageKey": null + }, + (v10/*: any*/) + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "PageInfo", + "kind": "LinkedField", + "name": "pageInfo", + "plural": false, + "selections": [ + (v12/*: any*/), + (v11/*: any*/), + (v13/*: any*/), + (v14/*: any*/) + ], + "storageKey": null + }, + (v15/*: any*/) + ], + "storageKey": "riskAssessments(first:50)" + }, + { + "alias": null, + "args": (v9/*: any*/), + "filters": (v16/*: any*/), + "handle": "connection", + "key": "VendorRiskAssessmentTabFragment_riskAssessments", + "kind": "LinkedHandle", + "name": "riskAssessments" } ], "type": "Vendor", @@ -384,20 +567,45 @@ return { } ], "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "Viewer", + "kind": "LinkedField", + "name": "viewer", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "User", + "kind": "LinkedField", + "name": "user", + "plural": false, + "selections": [ + (v7/*: any*/), + (v3/*: any*/) + ], + "storageKey": null + }, + (v3/*: any*/) + ], + "storageKey": null } ] }, "params": { - "cacheID": "f4827b073b1e66d0f2fa29283f276ac1", + "cacheID": "a57eb59eb17a2ad6d4f72f9174665561", "id": null, "metadata": {}, "name": "VendorGraphNodeQuery", "operationKind": "query", - "text": "query VendorGraphNodeQuery(\n $vendorId: ID!\n) {\n node(id: $vendorId) {\n __typename\n ... on Vendor {\n id\n name\n websiteUrl\n ...useVendorFormFragment\n ...VendorComplianceTabFragment\n }\n id\n }\n}\n\nfragment VendorComplianceTabFragment on Vendor {\n complianceReports(first: 50) {\n edges {\n node {\n id\n ...VendorComplianceTabFragment_report\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment VendorComplianceTabFragment_report on VendorComplianceReport {\n id\n reportDate\n validUntil\n reportName\n fileUrl\n fileSize\n}\n\nfragment useVendorFormFragment on Vendor {\n id\n name\n description\n statusPageUrl\n termsOfServiceUrl\n privacyPolicyUrl\n serviceLevelAgreementUrl\n dataProcessingAgreementUrl\n websiteUrl\n legalName\n headquarterAddress\n certifications\n securityPageUrl\n trustPageUrl\n businessOwner {\n id\n }\n securityOwner {\n id\n }\n}\n" + "text": "query VendorGraphNodeQuery(\n $vendorId: ID!\n $organizationId: ID!\n) {\n node(id: $vendorId) {\n __typename\n ... on Vendor {\n id\n name\n websiteUrl\n ...useVendorFormFragment\n ...VendorComplianceTabFragment\n ...VendorRiskAssessmentTabFragment\n }\n id\n }\n viewer {\n user {\n people(organizationId: $organizationId) {\n id\n }\n id\n }\n id\n }\n}\n\nfragment VendorComplianceTabFragment on Vendor {\n complianceReports(first: 50) {\n edges {\n node {\n id\n ...VendorComplianceTabFragment_report\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment VendorComplianceTabFragment_report on VendorComplianceReport {\n id\n reportDate\n validUntil\n reportName\n fileUrl\n fileSize\n}\n\nfragment VendorRiskAssessmentTabFragment on Vendor {\n id\n riskAssessments(first: 50) {\n edges {\n node {\n id\n ...VendorRiskAssessmentTabFragment_assessment\n __typename\n }\n cursor\n }\n pageInfo {\n hasNextPage\n endCursor\n hasPreviousPage\n startCursor\n }\n }\n}\n\nfragment VendorRiskAssessmentTabFragment_assessment on VendorRiskAssessment {\n id\n assessedAt\n assessedBy {\n id\n fullName\n }\n expiresAt\n dataSensitivity\n businessImpact\n notes\n}\n\nfragment useVendorFormFragment on Vendor {\n id\n name\n description\n statusPageUrl\n termsOfServiceUrl\n privacyPolicyUrl\n serviceLevelAgreementUrl\n dataProcessingAgreementUrl\n websiteUrl\n legalName\n headquarterAddress\n certifications\n securityPageUrl\n trustPageUrl\n businessOwner {\n id\n }\n securityOwner {\n id\n }\n}\n" } }; })(); -(node as any).hash = "97226bc4f316909815617a30a3a88cd9"; +(node as any).hash = "a4d080fd9c239011fa78c812153ed8da"; export default node; diff --git a/apps/console2/src/pages/organizations/measures/MeasuresPage.tsx b/apps/console2/src/pages/organizations/measures/MeasuresPage.tsx index 660afee76..0e92692c1 100644 --- a/apps/console2/src/pages/organizations/measures/MeasuresPage.tsx +++ b/apps/console2/src/pages/organizations/measures/MeasuresPage.tsx @@ -38,17 +38,10 @@ import type { MeasuresPageFragment$key, } from "./__generated__/MeasuresPageFragment.graphql"; import { groupBy, objectKeys, slugify, sprintf } from "@probo/helpers"; -import { - useEffect, - useMemo, - useRef, - useState, - type ChangeEventHandler, -} from "react"; +import { useMemo, useRef, useState, type ChangeEventHandler } from "react"; import type { NodeOf } from "/types"; import type { MeasuresPageImportMutation } from "./__generated__/MeasuresPageImportMutation.graphql"; import { useMutationWithToasts } from "/hooks/useMutationWithToasts"; -import { useToggle } from "@probo/hooks"; import { useOrganizationId } from "/hooks/useOrganizationId"; import { Link, useParams } from "react-router"; diff --git a/apps/console2/src/pages/organizations/people/PeopleListPage.tsx b/apps/console2/src/pages/organizations/people/PeopleListPage.tsx index 424b28d42..c48788812 100644 --- a/apps/console2/src/pages/organizations/people/PeopleListPage.tsx +++ b/apps/console2/src/pages/organizations/people/PeopleListPage.tsx @@ -10,7 +10,6 @@ import { Avatar, ActionDropdown, DropdownItem, - IconPencil, IconTrashCan, } from "@probo/ui"; import { useTranslate } from "@probo/i18n"; diff --git a/apps/console2/src/pages/organizations/vendors/VendorDetailPage.tsx b/apps/console2/src/pages/organizations/vendors/VendorDetailPage.tsx index c77685514..a547c6b51 100644 --- a/apps/console2/src/pages/organizations/vendors/VendorDetailPage.tsx +++ b/apps/console2/src/pages/organizations/vendors/VendorDetailPage.tsx @@ -88,9 +88,14 @@ export default function VendorDetailPage(props: Props) { > {__("Compliance reports")} + + {__("Risk Assessment")} + - + ); } diff --git a/apps/console2/src/pages/organizations/vendors/VendorsPage.tsx b/apps/console2/src/pages/organizations/vendors/VendorsPage.tsx index a89b4b6cc..477a42497 100644 --- a/apps/console2/src/pages/organizations/vendors/VendorsPage.tsx +++ b/apps/console2/src/pages/organizations/vendors/VendorsPage.tsx @@ -21,14 +21,14 @@ import { usePreloadedQuery, type PreloadedQuery } from "react-relay"; import { useOrganizationId } from "/hooks/useOrganizationId"; import { faviconUrl } from "@probo/helpers"; import type { NodeOf } from "/types"; -import { CreateVendorDialog } from "./CreateVendorDialog"; +import { CreateVendorDialog } from "./dialogs/CreateVendorDialog"; import { useDeleteVendor, vendorsQuery } from "/hooks/graph/VendorGraph"; import type { VendorGraphListQuery, VendorGraphListQuery$data, } from "/hooks/graph/__generated__/VendorGraphListQuery.graphql"; -type Vendor = NodeOf["vendors"]>; +type Vendor = NodeOf; type Props = { queryRef: PreloadedQuery; diff --git a/apps/console2/src/pages/organizations/vendors/dialogs/CreateRiskAssessmentDialog.tsx b/apps/console2/src/pages/organizations/vendors/dialogs/CreateRiskAssessmentDialog.tsx new file mode 100644 index 000000000..ebfb1ea50 --- /dev/null +++ b/apps/console2/src/pages/organizations/vendors/dialogs/CreateRiskAssessmentDialog.tsx @@ -0,0 +1,143 @@ +import { useTranslate } from "@probo/i18n"; +import { + Breadcrumb, + Button, + Dialog, + DialogContent, + DialogFooter, + Field, + ImpactOptions, + SentitivityOptions, + useDialogRef, +} from "@probo/ui"; +import { type ReactNode } from "react"; +import { graphql } from "relay-runtime"; +import { useMutationWithToasts } from "/hooks/useMutationWithToasts"; +import { z } from "zod"; +import { useFormWithSchema } from "/hooks/useFormWithSchema"; +import { ControlledField } from "/components/form/ControlledField"; + +type Props = { + children: ReactNode; + connection: string; + vendorId: string; + peopleId: string; +}; + +const createRiskAssessmentMutation = graphql` + mutation CreateRiskAssessmentDialogMutation( + $input: CreateVendorRiskAssessmentInput! + $connections: [ID!]! + ) { + createVendorRiskAssessment(input: $input) { + vendorRiskAssessmentEdge @prependEdge(connections: $connections) { + node { + ...VendorRiskAssessmentTabFragment_assessment + } + } + } + } +`; + +const schema = z.object({ + dataSensitivity: z.enum(["NONE", "LOW", "MEDIUM", "HIGH", "CRITICAL"]), + businessImpact: z.enum(["LOW", "MEDIUM", "HIGH", "CRITICAL"]), + notes: z.string().optional(), +}); + +/** + * Dialog to create or update a riskassessment + */ +export function CreateRiskAssessmentDialog({ + children, + connection, + vendorId, + peopleId, +}: Props) { + const { __ } = useTranslate(); + + const { register, handleSubmit, formState, reset, control } = + useFormWithSchema(schema, { + defaultValues: { + dataSensitivity: "LOW", + businessImpact: "LOW", + }, + }); + const [createRiskAssessment, isLoading] = useMutationWithToasts( + createRiskAssessmentMutation, + { + successMessage: __("Risk Assessment created successfully."), + errorMessage: __("Failed to create Risk Assessment. Please try again."), + } + ); + + const onSubmit = handleSubmit((data) => { + const nextYear = new Date(); + nextYear.setFullYear(nextYear.getFullYear() + 1); + createRiskAssessment({ + variables: { + input: { + ...data, + vendorId, + assessedBy: peopleId, + expiresAt: nextYear.toISOString(), + }, + connections: [connection!], + }, + onSuccess: () => { + dialogRef.current?.close(); + reset(); + }, + }); + }); + + const dialogRef = useDialogRef(); + + return ( + + } + > +
+ + + + + + + + + + + + +
+
+ ); +} diff --git a/apps/console2/src/pages/organizations/vendors/CreateVendorDialog.tsx b/apps/console2/src/pages/organizations/vendors/dialogs/CreateVendorDialog.tsx similarity index 100% rename from apps/console2/src/pages/organizations/vendors/CreateVendorDialog.tsx rename to apps/console2/src/pages/organizations/vendors/dialogs/CreateVendorDialog.tsx diff --git a/apps/console2/src/pages/organizations/vendors/dialogs/__generated__/CreateRiskAssessmentDialogMutation.graphql.ts b/apps/console2/src/pages/organizations/vendors/dialogs/__generated__/CreateRiskAssessmentDialogMutation.graphql.ts new file mode 100644 index 000000000..f13cda8e0 --- /dev/null +++ b/apps/console2/src/pages/organizations/vendors/dialogs/__generated__/CreateRiskAssessmentDialogMutation.graphql.ts @@ -0,0 +1,246 @@ +/** + * @generated SignedSource<> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type BusinessImpact = "CRITICAL" | "HIGH" | "LOW" | "MEDIUM"; +export type DataSensitivity = "CRITICAL" | "HIGH" | "LOW" | "MEDIUM" | "NONE"; +export type CreateVendorRiskAssessmentInput = { + assessedBy: string; + businessImpact: BusinessImpact; + dataSensitivity: DataSensitivity; + expiresAt: any; + notes?: string | null | undefined; + vendorId: string; +}; +export type CreateRiskAssessmentDialogMutation$variables = { + connections: ReadonlyArray; + input: CreateVendorRiskAssessmentInput; +}; +export type CreateRiskAssessmentDialogMutation$data = { + readonly createVendorRiskAssessment: { + readonly vendorRiskAssessmentEdge: { + readonly node: { + readonly " $fragmentSpreads": FragmentRefs<"VendorRiskAssessmentTabFragment_assessment">; + }; + }; + }; +}; +export type CreateRiskAssessmentDialogMutation = { + response: CreateRiskAssessmentDialogMutation$data; + variables: CreateRiskAssessmentDialogMutation$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": "id", + "storageKey": null +}; +return { + "fragment": { + "argumentDefinitions": [ + (v0/*: any*/), + (v1/*: any*/) + ], + "kind": "Fragment", + "metadata": null, + "name": "CreateRiskAssessmentDialogMutation", + "selections": [ + { + "alias": null, + "args": (v2/*: any*/), + "concreteType": "CreateVendorRiskAssessmentPayload", + "kind": "LinkedField", + "name": "createVendorRiskAssessment", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "VendorRiskAssessmentEdge", + "kind": "LinkedField", + "name": "vendorRiskAssessmentEdge", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "VendorRiskAssessment", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + { + "args": null, + "kind": "FragmentSpread", + "name": "VendorRiskAssessmentTabFragment_assessment" + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "type": "Mutation", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [ + (v1/*: any*/), + (v0/*: any*/) + ], + "kind": "Operation", + "name": "CreateRiskAssessmentDialogMutation", + "selections": [ + { + "alias": null, + "args": (v2/*: any*/), + "concreteType": "CreateVendorRiskAssessmentPayload", + "kind": "LinkedField", + "name": "createVendorRiskAssessment", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "VendorRiskAssessmentEdge", + "kind": "LinkedField", + "name": "vendorRiskAssessmentEdge", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "VendorRiskAssessment", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v3/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "assessedAt", + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "People", + "kind": "LinkedField", + "name": "assessedBy", + "plural": false, + "selections": [ + (v3/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "fullName", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "expiresAt", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "dataSensitivity", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "businessImpact", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "notes", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "filters": null, + "handle": "prependEdge", + "key": "", + "kind": "LinkedHandle", + "name": "vendorRiskAssessmentEdge", + "handleArgs": [ + { + "kind": "Variable", + "name": "connections", + "variableName": "connections" + } + ] + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "c55703ef9adb6c6aabf79aafe6cbeb89", + "id": null, + "metadata": {}, + "name": "CreateRiskAssessmentDialogMutation", + "operationKind": "mutation", + "text": "mutation CreateRiskAssessmentDialogMutation(\n $input: CreateVendorRiskAssessmentInput!\n) {\n createVendorRiskAssessment(input: $input) {\n vendorRiskAssessmentEdge {\n node {\n ...VendorRiskAssessmentTabFragment_assessment\n id\n }\n }\n }\n}\n\nfragment VendorRiskAssessmentTabFragment_assessment on VendorRiskAssessment {\n id\n assessedAt\n assessedBy {\n id\n fullName\n }\n expiresAt\n dataSensitivity\n businessImpact\n notes\n}\n" + } +}; +})(); + +(node as any).hash = "1e6753dbe2856b5a1809c15a81eb9861"; + +export default node; diff --git a/apps/console2/src/pages/organizations/vendors/tabs/VendorRiskAssessmentTab.tsx b/apps/console2/src/pages/organizations/vendors/tabs/VendorRiskAssessmentTab.tsx new file mode 100644 index 000000000..e16f89f00 --- /dev/null +++ b/apps/console2/src/pages/organizations/vendors/tabs/VendorRiskAssessmentTab.tsx @@ -0,0 +1,205 @@ +import { useOutletContext } from "react-router"; +import { graphql } from "relay-runtime"; +import type { VendorRiskAssessmentTabFragment$key } from "./__generated__/VendorRiskAssessmentTabFragment.graphql"; +import { useTranslate } from "@probo/i18n"; +import { usePageTitle } from "@probo/hooks"; +import { + Button, + Tbody, + Td, + Th, + Thead, + Tr, + IconPlusLarge, + RiskBadge, + Badge, +} from "@probo/ui"; +import { useFragment, useRefetchableFragment } from "react-relay"; +import type { VendorRiskAssessmentTabFragment_assessment$key } from "./__generated__/VendorRiskAssessmentTabFragment_assessment.graphql"; +import { SortableTable, SortableTh } from "/components/SortableTable"; +import { CreateRiskAssessmentDialog } from "../dialogs/CreateRiskAssessmentDialog"; +import clsx from "clsx"; +import { useState } from "react"; + +const riskAssessmentsFragment = graphql` + fragment VendorRiskAssessmentTabFragment on Vendor + @refetchable(queryName: "VendorRiskAssessmentTabQuery") + @argumentDefinitions( + first: { type: "Int", defaultValue: 50 } + order: { type: "VendorRiskAssessmentOrder", defaultValue: null } + after: { type: "CursorKey", defaultValue: null } + before: { type: "CursorKey", defaultValue: null } + last: { type: "Int", defaultValue: null } + ) { + id + + riskAssessments( + first: $first + after: $after + last: $last + before: $before + orderBy: $order + ) @connection(key: "VendorRiskAssessmentTabFragment_riskAssessments") { + __id + edges { + node { + id + ...VendorRiskAssessmentTabFragment_assessment + } + } + pageInfo { + hasNextPage + endCursor + } + } + } +`; + +const riskAssessmentFragment = graphql` + fragment VendorRiskAssessmentTabFragment_assessment on VendorRiskAssessment { + id + assessedAt + assessedBy { + id + fullName + } + expiresAt + dataSensitivity + businessImpact + notes + } +`; + +export default function VendorRiskAssessmentTab() { + const { vendor, peopleId } = useOutletContext<{ + vendor: VendorRiskAssessmentTabFragment$key & { name: string; id: string }; + peopleId: string; + }>(); + const [data, refetch] = useRefetchableFragment( + riskAssessmentsFragment, + vendor + ); + const assessments = data.riskAssessments.edges.map((edge) => edge.node); + const { __ } = useTranslate(); + const [expanded, setExpanded] = useState(null); + + usePageTitle(vendor.name + " - " + __("Risk Assessments")); + + if (assessments.length === 0) { + return ( +
+ {__("No risk assessments found")} + + + +
+ ); + } + + return ( +
+
+ + + +
+
+ + + + {__("Assessed By")} + {__("Expires")} + {__("Data sensitivity")} + {__("Business impact")} + + + + {assessments.map((assessment) => ( + + setExpanded((prev) => + prev === assessment.id ? null : assessment.id + ) + } + /> + ))} + + +
+
+ ); +} + +type AssessmentRowProps = { + assessmentKey: VendorRiskAssessmentTabFragment_assessment$key; + onClick: (id: string) => void; + isExpanded: boolean; +}; + +function AssessmentRow(props: AssessmentRowProps) { + const { __ } = useTranslate(); + const assessment = + useFragment( + riskAssessmentFragment, + props.assessmentKey + ); + const { relativeDateFormat } = useTranslate(); + const isExpired = new Date(assessment.expiresAt) < new Date(); + + return ( + <> + props.onClick(assessment.id)} + > + + {assessment.assessedBy?.fullName} + + ({relativeDateFormat(assessment.assessedAt)}) + + + +
+ {relativeDateFormat(assessment.expiresAt)} + {isExpired && {__("Expired")}} +
+ + + + + + + + + {props.isExpanded && ( + + +
+
{__("Notes")}:
+

{assessment.notes}

+
+ + + )} + + ); +} diff --git a/apps/console2/src/pages/organizations/vendors/tabs/__generated__/VendorRiskAssessmentTabFragment.graphql.ts b/apps/console2/src/pages/organizations/vendors/tabs/__generated__/VendorRiskAssessmentTabFragment.graphql.ts new file mode 100644 index 000000000..69b30470e --- /dev/null +++ b/apps/console2/src/pages/organizations/vendors/tabs/__generated__/VendorRiskAssessmentTabFragment.graphql.ts @@ -0,0 +1,229 @@ +/** + * @generated SignedSource<> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ReaderFragment } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type VendorRiskAssessmentTabFragment$data = { + readonly id: string; + readonly riskAssessments: { + readonly __id: string; + readonly edges: ReadonlyArray<{ + readonly node: { + readonly id: string; + readonly " $fragmentSpreads": FragmentRefs<"VendorRiskAssessmentTabFragment_assessment">; + }; + }>; + readonly pageInfo: { + readonly endCursor: any | null | undefined; + readonly hasNextPage: boolean; + }; + }; + readonly " $fragmentType": "VendorRiskAssessmentTabFragment"; +}; +export type VendorRiskAssessmentTabFragment$key = { + readonly " $data"?: VendorRiskAssessmentTabFragment$data; + readonly " $fragmentSpreads": FragmentRefs<"VendorRiskAssessmentTabFragment">; +}; + +import VendorRiskAssessmentTabQuery_graphql from './VendorRiskAssessmentTabQuery.graphql'; + +const node: ReaderFragment = (function(){ +var v0 = [ + "riskAssessments" +], +v1 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}; +return { + "argumentDefinitions": [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "after" + }, + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "before" + }, + { + "defaultValue": 50, + "kind": "LocalArgument", + "name": "first" + }, + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "last" + }, + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "order" + } + ], + "kind": "Fragment", + "metadata": { + "connection": [ + { + "count": null, + "cursor": null, + "direction": "bidirectional", + "path": (v0/*: any*/) + } + ], + "refetch": { + "connection": { + "forward": { + "count": "first", + "cursor": "after" + }, + "backward": { + "count": "last", + "cursor": "before" + }, + "path": (v0/*: any*/) + }, + "fragmentPathInResult": [ + "node" + ], + "operation": VendorRiskAssessmentTabQuery_graphql, + "identifierInfo": { + "identifierField": "id", + "identifierQueryVariableName": "id" + } + } + }, + "name": "VendorRiskAssessmentTabFragment", + "selections": [ + (v1/*: any*/), + { + "alias": "riskAssessments", + "args": [ + { + "kind": "Variable", + "name": "orderBy", + "variableName": "order" + } + ], + "concreteType": "VendorRiskAssessmentConnection", + "kind": "LinkedField", + "name": "__VendorRiskAssessmentTabFragment_riskAssessments_connection", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "VendorRiskAssessmentEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "VendorRiskAssessment", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v1/*: any*/), + { + "args": null, + "kind": "FragmentSpread", + "name": "VendorRiskAssessmentTabFragment_assessment" + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "cursor", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "PageInfo", + "kind": "LinkedField", + "name": "pageInfo", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "hasNextPage", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "endCursor", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "hasPreviousPage", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "startCursor", + "storageKey": null + } + ], + "storageKey": null + }, + { + "kind": "ClientExtension", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__id", + "storageKey": null + } + ] + } + ], + "storageKey": null + } + ], + "type": "Vendor", + "abstractKey": null +}; +})(); + +(node as any).hash = "0677e45996571e06b832e4600939db5b"; + +export default node; diff --git a/apps/console2/src/pages/organizations/vendors/tabs/__generated__/VendorRiskAssessmentTabFragment_assessment.graphql.ts b/apps/console2/src/pages/organizations/vendors/tabs/__generated__/VendorRiskAssessmentTabFragment_assessment.graphql.ts new file mode 100644 index 000000000..d9cc25ac2 --- /dev/null +++ b/apps/console2/src/pages/organizations/vendors/tabs/__generated__/VendorRiskAssessmentTabFragment_assessment.graphql.ts @@ -0,0 +1,110 @@ +/** + * @generated SignedSource<<68ddff15cd65f5367f7e9ae6b3c38f34>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ReaderFragment } from 'relay-runtime'; +export type BusinessImpact = "CRITICAL" | "HIGH" | "LOW" | "MEDIUM"; +export type DataSensitivity = "CRITICAL" | "HIGH" | "LOW" | "MEDIUM" | "NONE"; +import { FragmentRefs } from "relay-runtime"; +export type VendorRiskAssessmentTabFragment_assessment$data = { + readonly assessedAt: any; + readonly assessedBy: { + readonly fullName: string; + readonly id: string; + }; + readonly businessImpact: BusinessImpact; + readonly dataSensitivity: DataSensitivity; + readonly expiresAt: any; + readonly id: string; + readonly notes: string | null | undefined; + readonly " $fragmentType": "VendorRiskAssessmentTabFragment_assessment"; +}; +export type VendorRiskAssessmentTabFragment_assessment$key = { + readonly " $data"?: VendorRiskAssessmentTabFragment_assessment$data; + readonly " $fragmentSpreads": FragmentRefs<"VendorRiskAssessmentTabFragment_assessment">; +}; + +const node: ReaderFragment = (function(){ +var v0 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}; +return { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "VendorRiskAssessmentTabFragment_assessment", + "selections": [ + (v0/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "assessedAt", + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "People", + "kind": "LinkedField", + "name": "assessedBy", + "plural": false, + "selections": [ + (v0/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "fullName", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "expiresAt", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "dataSensitivity", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "businessImpact", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "notes", + "storageKey": null + } + ], + "type": "VendorRiskAssessment", + "abstractKey": null +}; +})(); + +(node as any).hash = "08d6de198077f9b5985258466bbc33c0"; + +export default node; diff --git a/apps/console2/src/pages/organizations/vendors/tabs/__generated__/VendorRiskAssessmentTabQuery.graphql.ts b/apps/console2/src/pages/organizations/vendors/tabs/__generated__/VendorRiskAssessmentTabQuery.graphql.ts new file mode 100644 index 000000000..acd729c53 --- /dev/null +++ b/apps/console2/src/pages/organizations/vendors/tabs/__generated__/VendorRiskAssessmentTabQuery.graphql.ts @@ -0,0 +1,370 @@ +/** + * @generated SignedSource<<0bca6db2c1a834fd326761b3544d00ae>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type OrderDirection = "ASC" | "DESC"; +export type VendorRiskAssessmentOrderField = "ASSESSED_AT" | "CREATED_AT" | "EXPIRES_AT"; +export type VendorRiskAssessmentOrder = { + direction: OrderDirection; + field: VendorRiskAssessmentOrderField; +}; +export type VendorRiskAssessmentTabQuery$variables = { + after?: any | null | undefined; + before?: any | null | undefined; + first?: number | null | undefined; + id: string; + last?: number | null | undefined; + order?: VendorRiskAssessmentOrder | null | undefined; +}; +export type VendorRiskAssessmentTabQuery$data = { + readonly node: { + readonly " $fragmentSpreads": FragmentRefs<"VendorRiskAssessmentTabFragment">; + }; +}; +export type VendorRiskAssessmentTabQuery = { + response: VendorRiskAssessmentTabQuery$data; + variables: VendorRiskAssessmentTabQuery$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = { + "defaultValue": null, + "kind": "LocalArgument", + "name": "after" +}, +v1 = { + "defaultValue": null, + "kind": "LocalArgument", + "name": "before" +}, +v2 = { + "defaultValue": 50, + "kind": "LocalArgument", + "name": "first" +}, +v3 = { + "defaultValue": null, + "kind": "LocalArgument", + "name": "id" +}, +v4 = { + "defaultValue": null, + "kind": "LocalArgument", + "name": "last" +}, +v5 = { + "defaultValue": null, + "kind": "LocalArgument", + "name": "order" +}, +v6 = [ + { + "kind": "Variable", + "name": "id", + "variableName": "id" + } +], +v7 = { + "kind": "Variable", + "name": "after", + "variableName": "after" +}, +v8 = { + "kind": "Variable", + "name": "before", + "variableName": "before" +}, +v9 = { + "kind": "Variable", + "name": "first", + "variableName": "first" +}, +v10 = { + "kind": "Variable", + "name": "last", + "variableName": "last" +}, +v11 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null +}, +v12 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}, +v13 = [ + (v7/*: any*/), + (v8/*: any*/), + (v9/*: any*/), + (v10/*: any*/), + { + "kind": "Variable", + "name": "orderBy", + "variableName": "order" + } +]; +return { + "fragment": { + "argumentDefinitions": [ + (v0/*: any*/), + (v1/*: any*/), + (v2/*: any*/), + (v3/*: any*/), + (v4/*: any*/), + (v5/*: any*/) + ], + "kind": "Fragment", + "metadata": null, + "name": "VendorRiskAssessmentTabQuery", + "selections": [ + { + "alias": null, + "args": (v6/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + { + "args": [ + (v7/*: any*/), + (v8/*: any*/), + (v9/*: any*/), + (v10/*: any*/), + { + "kind": "Variable", + "name": "order", + "variableName": "order" + } + ], + "kind": "FragmentSpread", + "name": "VendorRiskAssessmentTabFragment" + } + ], + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [ + (v0/*: any*/), + (v1/*: any*/), + (v2/*: any*/), + (v4/*: any*/), + (v5/*: any*/), + (v3/*: any*/) + ], + "kind": "Operation", + "name": "VendorRiskAssessmentTabQuery", + "selections": [ + { + "alias": null, + "args": (v6/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v11/*: any*/), + (v12/*: any*/), + { + "kind": "InlineFragment", + "selections": [ + { + "alias": null, + "args": (v13/*: any*/), + "concreteType": "VendorRiskAssessmentConnection", + "kind": "LinkedField", + "name": "riskAssessments", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "VendorRiskAssessmentEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "VendorRiskAssessment", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v12/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "assessedAt", + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "People", + "kind": "LinkedField", + "name": "assessedBy", + "plural": false, + "selections": [ + (v12/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "fullName", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "expiresAt", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "dataSensitivity", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "businessImpact", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "notes", + "storageKey": null + }, + (v11/*: any*/) + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "cursor", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "PageInfo", + "kind": "LinkedField", + "name": "pageInfo", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "hasNextPage", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "endCursor", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "hasPreviousPage", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "startCursor", + "storageKey": null + } + ], + "storageKey": null + }, + { + "kind": "ClientExtension", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__id", + "storageKey": null + } + ] + } + ], + "storageKey": null + }, + { + "alias": null, + "args": (v13/*: any*/), + "filters": [ + "orderBy" + ], + "handle": "connection", + "key": "VendorRiskAssessmentTabFragment_riskAssessments", + "kind": "LinkedHandle", + "name": "riskAssessments" + } + ], + "type": "Vendor", + "abstractKey": null + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "26ab0b864c4e22a21c078f70ed7caf93", + "id": null, + "metadata": {}, + "name": "VendorRiskAssessmentTabQuery", + "operationKind": "query", + "text": "query VendorRiskAssessmentTabQuery(\n $after: CursorKey = null\n $before: CursorKey = null\n $first: Int = 50\n $last: Int = null\n $order: VendorRiskAssessmentOrder = null\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...VendorRiskAssessmentTabFragment_16fISc\n id\n }\n}\n\nfragment VendorRiskAssessmentTabFragment_16fISc on Vendor {\n id\n riskAssessments(first: $first, after: $after, last: $last, before: $before, orderBy: $order) {\n edges {\n node {\n id\n ...VendorRiskAssessmentTabFragment_assessment\n __typename\n }\n cursor\n }\n pageInfo {\n hasNextPage\n endCursor\n hasPreviousPage\n startCursor\n }\n }\n}\n\nfragment VendorRiskAssessmentTabFragment_assessment on VendorRiskAssessment {\n id\n assessedAt\n assessedBy {\n id\n fullName\n }\n expiresAt\n dataSensitivity\n businessImpact\n notes\n}\n" + } +}; +})(); + +(node as any).hash = "0677e45996571e06b832e4600939db5b"; + +export default node; diff --git a/apps/console2/src/routes/vendorRoutes.ts b/apps/console2/src/routes/vendorRoutes.ts index db79d99c6..df439f857 100644 --- a/apps/console2/src/routes/vendorRoutes.ts +++ b/apps/console2/src/routes/vendorRoutes.ts @@ -16,9 +16,10 @@ export const vendorRoutes = [ { path: "vendors/:vendorId", fallback: PageSkeleton, - queryLoader: ({ vendorId }) => + queryLoader: ({ vendorId, organizationId }) => loadQuery(relayEnvironment, vendorNodeQuery, { vendorId, + organizationId, }), Component: lazy( () => import("../pages/organizations/vendors/VendorDetailPage") @@ -46,6 +47,15 @@ export const vendorRoutes = [ import("../pages/organizations/vendors/tabs/VendorComplianceTab") ), }, + { + path: "risks", + Component: lazy( + () => + import( + "../pages/organizations/vendors/tabs/VendorRiskAssessmentTab" + ) + ), + }, ], }, ] satisfies AppRoute[]; diff --git a/packages/i18n/TranslatorProvider.tsx b/packages/i18n/TranslatorProvider.tsx index 75ebc7561..a5677ebf2 100644 --- a/packages/i18n/TranslatorProvider.tsx +++ b/packages/i18n/TranslatorProvider.tsx @@ -48,6 +48,23 @@ export function TranslatorProvider({ ); } +const MINUTES = 60_000; +const HOURS = MINUTES * 60; +const DAYS = HOURS * 24; +const WEEKS = DAYS * 7; +const MONTHS = DAYS * 30; +const YEARS = DAYS * 365; + +const relativeFormat = [ + { limit: YEARS, unit: "years" }, + { limit: MONTHS, unit: "months" }, + { limit: WEEKS, unit: "weeks" }, + { limit: DAYS, unit: "days" }, + { limit: HOURS, unit: "hours" }, + { limit: MINUTES, unit: "minutes" }, + { limit: 0, unit: "seconds" }, +] as const; + export function useTranslate() { const { translate, lang } = useContext(TranslatorContext); const dateFormat = ( @@ -67,12 +84,33 @@ export function useTranslate() { } return new Intl.DateTimeFormat(lang, options).format(date); }; + + const relativeDateFormat = ( + data: Date | string | null | undefined, + options: Intl.RelativeTimeFormatOptions = { + style: "long", + } + ) => { + if (!data) { + return ""; + } + const distanceInSeconds = + (data instanceof Date ? data.getTime() : parseDate(data).getTime()) - + Date.now(); + const formatter = new Intl.RelativeTimeFormat(lang, options); + for (const { limit, unit } of relativeFormat) { + if (Math.abs(distanceInSeconds) > limit) { + return formatter.format(Math.round(distanceInSeconds / limit), unit); + } + } + return ""; + }; + return { lang, __: translate, - dateFormat: dateFormat, - + relativeDateFormat, dateTimeFormat: ( date: Date | string | null | undefined, options: Intl.DateTimeFormatOptions = { diff --git a/packages/i18n/package.json b/packages/i18n/package.json index 4a3cff806..2aa44e3cb 100644 --- a/packages/i18n/package.json +++ b/packages/i18n/package.json @@ -1,19 +1,16 @@ { "name": "@probo/i18n", "version": "1.0.0", - "description": "", - "main": "./TranslatorProvider.tsx", - "scripts": {}, - "peerDependencies": { - "react": "^18.0" - }, - "keywords": [], "author": "", - "license": "ISC", + "main": "./TranslatorProvider.tsx", "devDependencies": { "@types/react": "^19.1.2" }, - "dependencies": { - "date-fns": "^4.1.0" - } + "peerDependencies": { + "react": "^18.0" + }, + "description": "", + "keywords": [], + "license": "ISC", + "scripts": {} } diff --git a/packages/ui/src/Atoms/Button/Button.tsx b/packages/ui/src/Atoms/Button/Button.tsx index 29b59bd72..5e30a1819 100644 --- a/packages/ui/src/Atoms/Button/Button.tsx +++ b/packages/ui/src/Atoms/Button/Button.tsx @@ -9,7 +9,7 @@ import { tv, type VariantProps } from "tailwind-variants"; import { Slot } from "../Slot"; export const button = tv({ - base: "flex items-center justify-center gap-[6px] px-3 py-2 rounded-full cursor-pointer text-sm font-medium h-8 focus:outline-none", + base: "flex items-center justify-center gap-[6px] px-3 py-2 rounded-full cursor-pointer text-sm font-medium h-8 focus:outline-none whitespace-nowrap", variants: { variant: { primary: diff --git a/packages/ui/src/Atoms/Dropzone/Dropzone.tsx b/packages/ui/src/Atoms/Dropzone/Dropzone.tsx index 7ed72d04e..31b381e6d 100644 --- a/packages/ui/src/Atoms/Dropzone/Dropzone.tsx +++ b/packages/ui/src/Atoms/Dropzone/Dropzone.tsx @@ -80,8 +80,6 @@ export function Dropzone(props: Props) { hasError: !!error, }); - console.log(error); - return (
diff --git a/packages/ui/src/Atoms/Select/Select.tsx b/packages/ui/src/Atoms/Select/Select.tsx index f71095cfe..deab06b88 100644 --- a/packages/ui/src/Atoms/Select/Select.tsx +++ b/packages/ui/src/Atoms/Select/Select.tsx @@ -15,16 +15,17 @@ import { IconChevronGrabberVertical } from "../Icons/IconChevronGrabberVertical. import { tv } from "tailwind-variants"; import { Children, type ComponentProps, type PropsWithChildren } from "react"; -type Props = PropsWithChildren< +type Props = PropsWithChildren< { id?: string; placeholder?: string; - onValueChange?: (s: string) => void; + onValueChange?: (s: NonNullable) => void; variant?: "default" | "editor" | "dashed"; invalid?: boolean; disabled?: boolean; className?: string; - } & Omit, "onChange"> + value?: T; + } & Omit, "onChange" | "value"> >; const select = tv({ @@ -70,19 +71,19 @@ const select = tv({ }, }); -export function Select({ +export function Select({ placeholder, children, onValueChange, value, ...props -}: Props) { +}: Props) { const { trigger, content, icon } = select({ ...props, }); return ( - + ) { diff --git a/packages/ui/src/Atoms/Table/Table.tsx b/packages/ui/src/Atoms/Table/Table.tsx index 8a30aa6aa..612dc1d9c 100644 --- a/packages/ui/src/Atoms/Table/Table.tsx +++ b/packages/ui/src/Atoms/Table/Table.tsx @@ -1,4 +1,9 @@ -import { createContext, useContext, type PropsWithChildren } from "react"; +import { + createContext, + useContext, + type HTMLAttributes, + type PropsWithChildren, +} from "react"; import { Card } from "../Card/Card"; import { Link } from "react-router"; import clsx from "clsx"; @@ -25,9 +30,13 @@ export function Thead({ children }: PropsWithChildren) { export function Th({ children, className, -}: PropsWithChildren<{ className?: string }>) { + width, +}: PropsWithChildren<{ className?: string; width?: number }>) { return ( - + {children} ); @@ -35,17 +44,21 @@ export function Th({ const TrContext = createContext({} as { to?: string }); -export function Tr({ children, to }: PropsWithChildren<{ to?: string }>) { +export function Tr({ + to, + className, + ...props +}: { to?: string } & HTMLAttributes) { return ( - {children} - + /> ); } @@ -63,15 +76,17 @@ export function Td({ noLink, className, width, -}: PropsWithChildren<{ + ...props +}: { noLink?: boolean; - className?: string; width?: number; -}>) { + colSpan?: number; +} & HTMLAttributes) { const { to } = useContext(TrContext); if (!to || noLink) { return ( @@ -81,6 +96,7 @@ export function Td({ } return ( {objectKeys(icons).map((k) => { diff --git a/packages/ui/src/Molecules/Select/ImpactOptions.stories.tsx b/packages/ui/src/Molecules/Select/ImpactOptions.stories.tsx new file mode 100644 index 000000000..d745b2fb0 --- /dev/null +++ b/packages/ui/src/Molecules/Select/ImpactOptions.stories.tsx @@ -0,0 +1,21 @@ +import { ImpactOptions } from "./ImpactOptions.tsx"; +import type { Meta, StoryObj } from "@storybook/react"; +import { Select } from "../../Atoms/Select/Select.tsx"; + +export default { + title: "Molecules/Select/ImpactSelect", + component: ImpactOptions, + argTypes: {}, +} satisfies Meta; + +type Story = StoryObj; + +export const Default: Story = { + render() { + return ( + + ); + }, +}; diff --git a/packages/ui/src/Molecules/Select/ImpactOptions.tsx b/packages/ui/src/Molecules/Select/ImpactOptions.tsx new file mode 100644 index 000000000..9810565ba --- /dev/null +++ b/packages/ui/src/Molecules/Select/ImpactOptions.tsx @@ -0,0 +1,47 @@ +import { Option } from "../../Atoms/Select/Select"; +import { useTranslate } from "@probo/i18n"; + +export function ImpactOptions() { + const { __ } = useTranslate(); + + const descriptions = { + LOW: { + label: __("Low"), + description: __("Minimal impact on business"), + }, + MEDIUM: { + label: __("Medium"), + description: __("Moderate impact on business"), + }, + HIGH: { + label: __("High"), + description: __("Significant business impact"), + }, + CRITICAL: { + label: __("Critical"), + description: __("Critical to business operations"), + }, + } as const; + + return ( + <> + {Object.entries(descriptions).map(([key, description]) => ( + + ))} + + ); +} diff --git a/packages/ui/src/Molecules/Select/SentitivityOptions.stories.tsx b/packages/ui/src/Molecules/Select/SentitivityOptions.stories.tsx new file mode 100644 index 000000000..c3a5432f2 --- /dev/null +++ b/packages/ui/src/Molecules/Select/SentitivityOptions.stories.tsx @@ -0,0 +1,21 @@ +import { SentitivityOptions } from "./SentitivityOptions.tsx"; +import type { Meta, StoryObj } from "@storybook/react"; +import { Select } from "../../Atoms/Select/Select.tsx"; + +export default { + title: "Molecules/Select/SentitivitySelect", + component: SentitivityOptions, + argTypes: {}, +} satisfies Meta; + +type Story = StoryObj; + +export const Default: Story = { + render() { + return ( + + ); + }, +}; diff --git a/packages/ui/src/Molecules/Select/SentitivityOptions.tsx b/packages/ui/src/Molecules/Select/SentitivityOptions.tsx new file mode 100644 index 000000000..f90a8a90e --- /dev/null +++ b/packages/ui/src/Molecules/Select/SentitivityOptions.tsx @@ -0,0 +1,51 @@ +import { Option } from "../../Atoms/Select/Select"; +import { useTranslate } from "@probo/i18n"; + +export function SentitivityOptions() { + const { __ } = useTranslate(); + + const descriptions = { + NONE: { + label: __("None"), + description: __("No sensitive data"), + }, + LOW: { + label: __("Low"), + description: __("Public or non-sensitive data"), + }, + MEDIUM: { + label: __("Medium"), + description: __("Internal/restricted data"), + }, + HIGH: { + label: __("High"), + description: __("Confidential data"), + }, + CRITICAL: { + label: __("Critical"), + description: __("Regulated/PII/financial data"), + }, + } as const; + + return ( + <> + {Object.entries(descriptions).map(([key, description]) => ( + + ))} + + ); +} diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index c94243139..17ecb226e 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -62,6 +62,8 @@ export { MeasureBadge } from "./Molecules/Badge/MeasureBadge"; export { MeasureImplementation } from "./Molecules/MeasureImplementation/MeasureImplementation"; export { FrameworkSelector } from "./Molecules/FrameworkSelector/FrameworkSelector"; export { DocumentTypeBadge } from "./Molecules/Badge/DocumentTypeBadge"; +export { SentitivityOptions } from "./Molecules/Select/SentitivityOptions"; +export { ImpactOptions } from "./Molecules/Select/ImpactOptions"; // Hooks export { useToast, Toasts } from "./Atoms/Toasts/Toasts";