From 0262bd1c3dfe80059aa1e4a5c76f4f27f359e0bc Mon Sep 17 00:00:00 2001 From: Sacha Al Himdani Date: Mon, 18 Aug 2025 14:57:53 +0200 Subject: [PATCH] Add compliance registries Signed-off-by: Sacha Al Himdani --- .../hooks/graph/ComplianceRegistryGraph.ts | 251 ++ ...anceRegistryGraphCreateMutation.graphql.ts | 382 +++ ...anceRegistryGraphDeleteMutation.graphql.ts | 132 + ...omplianceRegistryGraphListQuery.graphql.ts | 361 ++ ...omplianceRegistryGraphNodeQuery.graphql.ts | 320 ++ ...anceRegistryGraphUpdateMutation.graphql.ts | 262 ++ apps/console/src/layouts/MainLayout.tsx | 8 +- .../ComplianceRegistriesPage.tsx | 264 ++ .../ComplianceRegistryDetailsPage.tsx | 315 ++ ...omplianceRegistriesPageFragment.graphql.ts | 346 ++ .../ComplianceRegistriesPageQuery.graphql.ts | 361 ++ ...ianceRegistriesPageRefetchQuery.graphql.ts | 371 ++ .../CreateComplianceRegistryDialog.tsx | 252 ++ .../NonconformityRegistryDetailsPage.tsx | 2 - apps/console/src/routes.tsx | 2 + .../src/routes/complianceRegistryRoutes.ts | 29 + packages/helpers/src/index.ts | 2 +- packages/helpers/src/registryStatus.ts | 16 +- pkg/coredata/compliance_registry.go | 348 ++ .../compliance_registry_order_field.go | 55 + pkg/coredata/compliance_registry_status.go | 60 + pkg/coredata/entity_type_reg.go | 1 + pkg/coredata/migrations/20250818T094916Z.sql | 42 + pkg/probo/compliance_registry_service.go | 300 ++ pkg/probo/service.go | 2 + pkg/server/api/console/v1/schema.graphql | 145 + pkg/server/api/console/v1/schema/schema.go | 3012 +++++++++++++++++ .../console/v1/types/compliance_registry.go | 77 + pkg/server/api/console/v1/types/types.go | 73 + pkg/server/api/console/v1/v1_resolver.go | 182 + 30 files changed, 7967 insertions(+), 6 deletions(-) create mode 100644 apps/console/src/hooks/graph/ComplianceRegistryGraph.ts create mode 100644 apps/console/src/hooks/graph/__generated__/ComplianceRegistryGraphCreateMutation.graphql.ts create mode 100644 apps/console/src/hooks/graph/__generated__/ComplianceRegistryGraphDeleteMutation.graphql.ts create mode 100644 apps/console/src/hooks/graph/__generated__/ComplianceRegistryGraphListQuery.graphql.ts create mode 100644 apps/console/src/hooks/graph/__generated__/ComplianceRegistryGraphNodeQuery.graphql.ts create mode 100644 apps/console/src/hooks/graph/__generated__/ComplianceRegistryGraphUpdateMutation.graphql.ts create mode 100644 apps/console/src/pages/organizations/complianceRegistries/ComplianceRegistriesPage.tsx create mode 100644 apps/console/src/pages/organizations/complianceRegistries/ComplianceRegistryDetailsPage.tsx create mode 100644 apps/console/src/pages/organizations/complianceRegistries/__generated__/ComplianceRegistriesPageFragment.graphql.ts create mode 100644 apps/console/src/pages/organizations/complianceRegistries/__generated__/ComplianceRegistriesPageQuery.graphql.ts create mode 100644 apps/console/src/pages/organizations/complianceRegistries/__generated__/ComplianceRegistriesPageRefetchQuery.graphql.ts create mode 100644 apps/console/src/pages/organizations/complianceRegistries/dialogs/CreateComplianceRegistryDialog.tsx create mode 100644 apps/console/src/routes/complianceRegistryRoutes.ts create mode 100644 pkg/coredata/compliance_registry.go create mode 100644 pkg/coredata/compliance_registry_order_field.go create mode 100644 pkg/coredata/compliance_registry_status.go create mode 100644 pkg/coredata/migrations/20250818T094916Z.sql create mode 100644 pkg/probo/compliance_registry_service.go create mode 100644 pkg/server/api/console/v1/types/compliance_registry.go diff --git a/apps/console/src/hooks/graph/ComplianceRegistryGraph.ts b/apps/console/src/hooks/graph/ComplianceRegistryGraph.ts new file mode 100644 index 000000000..232cf45da --- /dev/null +++ b/apps/console/src/hooks/graph/ComplianceRegistryGraph.ts @@ -0,0 +1,251 @@ +import { graphql } from "relay-runtime"; +import { useMutation } from "react-relay"; +import { useConfirm } from "@probo/ui"; +import { useTranslate } from "@probo/i18n"; +import { promisifyMutation, sprintf } from "@probo/helpers"; +import { useMutationWithToasts } from "../useMutationWithToasts"; + +export const ComplianceRegistriesConnectionKey = "ComplianceRegistriesPage_complianceRegistries"; + +export const complianceRegistriesQuery = graphql` + query ComplianceRegistryGraphListQuery($organizationId: ID!) { + node(id: $organizationId) { + ... on Organization { + ...ComplianceRegistriesPageFragment + } + } + } +`; + +export const complianceRegistryNodeQuery = graphql` + query ComplianceRegistryGraphNodeQuery($complianceRegistryId: ID!) { + node(id: $complianceRegistryId) { + ... on ComplianceRegistry { + id + referenceId + area + source + requirement + actionsToBeImplemented + regulator + lastReviewDate + dueDate + status + audit { + id + name + framework { + id + name + } + } + owner { + id + fullName + } + organization { + id + name + } + createdAt + updatedAt + } + } + } +`; + +export const createComplianceRegistryMutation = graphql` + mutation ComplianceRegistryGraphCreateMutation( + $input: CreateComplianceRegistryInput! + $connections: [ID!]! + ) { + createComplianceRegistry(input: $input) { + complianceRegistryEdge @prependEdge(connections: $connections) { + node { + id + referenceId + area + source + requirement + actionsToBeImplemented + regulator + lastReviewDate + dueDate + status + audit { + id + name + framework { + name + } + } + owner { + id + fullName + } + createdAt + } + } + } + } +`; + +export const updateComplianceRegistryMutation = graphql` + mutation ComplianceRegistryGraphUpdateMutation($input: UpdateComplianceRegistryInput!) { + updateComplianceRegistry(input: $input) { + complianceRegistry { + id + referenceId + area + source + requirement + actionsToBeImplemented + regulator + lastReviewDate + dueDate + status + owner { + id + fullName + } + audit { + id + name + framework { + id + name + } + } + updatedAt + } + } + } +`; + +export const deleteComplianceRegistryMutation = graphql` + mutation ComplianceRegistryGraphDeleteMutation( + $input: DeleteComplianceRegistryInput! + $connections: [ID!]! + ) { + deleteComplianceRegistry(input: $input) { + deletedComplianceRegistryId @deleteEdge(connections: $connections) + } + } +`; + +export const useDeleteComplianceRegistry = ( + registry: { id: string; referenceId: string }, + connectionId: string +) => { + const { __ } = useTranslate(); + const [mutate] = useMutationWithToasts(deleteComplianceRegistryMutation, { + successMessage: __("Compliance registry entry deleted successfully"), + errorMessage: __("Failed to delete compliance registry entry"), + }); + const confirm = useConfirm(); + + return () => { + confirm( + () => + mutate({ + variables: { + input: { + complianceRegistryId: registry.id, + }, + connections: [connectionId], + }, + }), + { + message: sprintf( + __( + "This will permanently delete the compliance registry entry %s. This action cannot be undone." + ), + registry.referenceId + ), + } + ); + }; +}; + +export const useCreateComplianceRegistry = (connectionId: string) => { + const [mutate] = useMutation(createComplianceRegistryMutation); + const { __ } = useTranslate(); + + return (input: { + organizationId: string; + referenceId: string; + area?: string; + source?: string; + auditId: string; + requirement?: string; + actionsToBeImplemented?: string; + regulator?: string; + ownerId: string; + lastReviewDate?: string; + dueDate?: string; + status: string; + }) => { + if (!input.organizationId) { + return alert(__("Failed to create compliance registry entry: organization is required")); + } + if (!input.referenceId) { + return alert(__("Failed to create compliance registry entry: reference ID is required")); + } + if (!input.auditId) { + return alert(__("Failed to create compliance registry entry: audit is required")); + } + if (!input.ownerId) { + return alert(__("Failed to create compliance registry entry: owner is required")); + } + + return promisifyMutation(mutate)({ + variables: { + input: { + organizationId: input.organizationId, + referenceId: input.referenceId, + area: input.area, + source: input.source, + auditId: input.auditId, + requirement: input.requirement, + actionsToBeImplemented: input.actionsToBeImplemented, + regulator: input.regulator, + ownerId: input.ownerId, + lastReviewDate: input.lastReviewDate, + dueDate: input.dueDate, + status: input.status || "OPEN", + }, + connections: [connectionId], + }, + }); + }; +}; + +export const useUpdateComplianceRegistry = () => { + const [mutate] = useMutation(updateComplianceRegistryMutation); + const { __ } = useTranslate(); + + return (input: { + id: string; + referenceId?: string; + area?: string; + source?: string; + auditId?: string; + requirement?: string; + actionsToBeImplemented?: string; + regulator?: string; + ownerId?: string; + lastReviewDate?: string; + dueDate?: string; + status?: string; + }) => { + if (!input.id) { + return alert(__("Failed to update compliance registry entry: registry ID is required")); + } + + return promisifyMutation(mutate)({ + variables: { + input, + }, + }); + }; +}; diff --git a/apps/console/src/hooks/graph/__generated__/ComplianceRegistryGraphCreateMutation.graphql.ts b/apps/console/src/hooks/graph/__generated__/ComplianceRegistryGraphCreateMutation.graphql.ts new file mode 100644 index 000000000..bf7a017bf --- /dev/null +++ b/apps/console/src/hooks/graph/__generated__/ComplianceRegistryGraphCreateMutation.graphql.ts @@ -0,0 +1,382 @@ +/** + * @generated SignedSource<> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +export type ComplianceRegistryStatus = "CLOSED" | "IN_PROGRESS" | "OPEN"; +export type CreateComplianceRegistryInput = { + actionsToBeImplemented?: string | null | undefined; + area?: string | null | undefined; + auditId: string; + dueDate?: any | null | undefined; + lastReviewDate?: any | null | undefined; + organizationId: string; + ownerId: string; + referenceId: string; + regulator?: string | null | undefined; + requirement?: string | null | undefined; + source?: string | null | undefined; + status: ComplianceRegistryStatus; +}; +export type ComplianceRegistryGraphCreateMutation$variables = { + connections: ReadonlyArray; + input: CreateComplianceRegistryInput; +}; +export type ComplianceRegistryGraphCreateMutation$data = { + readonly createComplianceRegistry: { + readonly complianceRegistryEdge: { + readonly node: { + readonly actionsToBeImplemented: string | null | undefined; + readonly area: string | null | undefined; + readonly audit: { + readonly framework: { + readonly name: string; + }; + readonly id: string; + readonly name: string | null | undefined; + }; + readonly createdAt: any; + readonly dueDate: any | null | undefined; + readonly id: string; + readonly lastReviewDate: any | null | undefined; + readonly owner: { + readonly fullName: string; + readonly id: string; + }; + readonly referenceId: string; + readonly regulator: string | null | undefined; + readonly requirement: string | null | undefined; + readonly source: string | null | undefined; + readonly status: ComplianceRegistryStatus; + }; + }; + }; +}; +export type ComplianceRegistryGraphCreateMutation = { + response: ComplianceRegistryGraphCreateMutation$data; + variables: ComplianceRegistryGraphCreateMutation$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, + "kind": "ScalarField", + "name": "referenceId", + "storageKey": null +}, +v5 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "area", + "storageKey": null +}, +v6 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "source", + "storageKey": null +}, +v7 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "requirement", + "storageKey": null +}, +v8 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "actionsToBeImplemented", + "storageKey": null +}, +v9 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "regulator", + "storageKey": null +}, +v10 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "lastReviewDate", + "storageKey": null +}, +v11 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "dueDate", + "storageKey": null +}, +v12 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "status", + "storageKey": null +}, +v13 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null +}, +v14 = { + "alias": null, + "args": null, + "concreteType": "People", + "kind": "LinkedField", + "name": "owner", + "plural": false, + "selections": [ + (v3/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "fullName", + "storageKey": null + } + ], + "storageKey": null +}, +v15 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "createdAt", + "storageKey": null +}; +return { + "fragment": { + "argumentDefinitions": [ + (v0/*: any*/), + (v1/*: any*/) + ], + "kind": "Fragment", + "metadata": null, + "name": "ComplianceRegistryGraphCreateMutation", + "selections": [ + { + "alias": null, + "args": (v2/*: any*/), + "concreteType": "CreateComplianceRegistryPayload", + "kind": "LinkedField", + "name": "createComplianceRegistry", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "ComplianceRegistryEdge", + "kind": "LinkedField", + "name": "complianceRegistryEdge", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "ComplianceRegistry", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v3/*: any*/), + (v4/*: any*/), + (v5/*: any*/), + (v6/*: any*/), + (v7/*: any*/), + (v8/*: any*/), + (v9/*: any*/), + (v10/*: any*/), + (v11/*: any*/), + (v12/*: any*/), + { + "alias": null, + "args": null, + "concreteType": "Audit", + "kind": "LinkedField", + "name": "audit", + "plural": false, + "selections": [ + (v3/*: any*/), + (v13/*: any*/), + { + "alias": null, + "args": null, + "concreteType": "Framework", + "kind": "LinkedField", + "name": "framework", + "plural": false, + "selections": [ + (v13/*: any*/) + ], + "storageKey": null + } + ], + "storageKey": null + }, + (v14/*: any*/), + (v15/*: any*/) + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "type": "Mutation", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [ + (v1/*: any*/), + (v0/*: any*/) + ], + "kind": "Operation", + "name": "ComplianceRegistryGraphCreateMutation", + "selections": [ + { + "alias": null, + "args": (v2/*: any*/), + "concreteType": "CreateComplianceRegistryPayload", + "kind": "LinkedField", + "name": "createComplianceRegistry", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "ComplianceRegistryEdge", + "kind": "LinkedField", + "name": "complianceRegistryEdge", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "ComplianceRegistry", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v3/*: any*/), + (v4/*: any*/), + (v5/*: any*/), + (v6/*: any*/), + (v7/*: any*/), + (v8/*: any*/), + (v9/*: any*/), + (v10/*: any*/), + (v11/*: any*/), + (v12/*: any*/), + { + "alias": null, + "args": null, + "concreteType": "Audit", + "kind": "LinkedField", + "name": "audit", + "plural": false, + "selections": [ + (v3/*: any*/), + (v13/*: any*/), + { + "alias": null, + "args": null, + "concreteType": "Framework", + "kind": "LinkedField", + "name": "framework", + "plural": false, + "selections": [ + (v13/*: any*/), + (v3/*: any*/) + ], + "storageKey": null + } + ], + "storageKey": null + }, + (v14/*: any*/), + (v15/*: any*/) + ], + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "filters": null, + "handle": "prependEdge", + "key": "", + "kind": "LinkedHandle", + "name": "complianceRegistryEdge", + "handleArgs": [ + { + "kind": "Variable", + "name": "connections", + "variableName": "connections" + } + ] + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "68c793eb099ce3e917992b3f8c22d551", + "id": null, + "metadata": {}, + "name": "ComplianceRegistryGraphCreateMutation", + "operationKind": "mutation", + "text": "mutation ComplianceRegistryGraphCreateMutation(\n $input: CreateComplianceRegistryInput!\n) {\n createComplianceRegistry(input: $input) {\n complianceRegistryEdge {\n node {\n id\n referenceId\n area\n source\n requirement\n actionsToBeImplemented\n regulator\n lastReviewDate\n dueDate\n status\n audit {\n id\n name\n framework {\n name\n id\n }\n }\n owner {\n id\n fullName\n }\n createdAt\n }\n }\n }\n}\n" + } +}; +})(); + +(node as any).hash = "50e1966d2f9cc1ed347a77f8f024125c"; + +export default node; diff --git a/apps/console/src/hooks/graph/__generated__/ComplianceRegistryGraphDeleteMutation.graphql.ts b/apps/console/src/hooks/graph/__generated__/ComplianceRegistryGraphDeleteMutation.graphql.ts new file mode 100644 index 000000000..61e097ba0 --- /dev/null +++ b/apps/console/src/hooks/graph/__generated__/ComplianceRegistryGraphDeleteMutation.graphql.ts @@ -0,0 +1,132 @@ +/** + * @generated SignedSource<<0d815d748a76df90cc5305fa28913125>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +export type DeleteComplianceRegistryInput = { + complianceRegistryId: string; +}; +export type ComplianceRegistryGraphDeleteMutation$variables = { + connections: ReadonlyArray; + input: DeleteComplianceRegistryInput; +}; +export type ComplianceRegistryGraphDeleteMutation$data = { + readonly deleteComplianceRegistry: { + readonly deletedComplianceRegistryId: string; + }; +}; +export type ComplianceRegistryGraphDeleteMutation = { + response: ComplianceRegistryGraphDeleteMutation$data; + variables: ComplianceRegistryGraphDeleteMutation$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": "deletedComplianceRegistryId", + "storageKey": null +}; +return { + "fragment": { + "argumentDefinitions": [ + (v0/*: any*/), + (v1/*: any*/) + ], + "kind": "Fragment", + "metadata": null, + "name": "ComplianceRegistryGraphDeleteMutation", + "selections": [ + { + "alias": null, + "args": (v2/*: any*/), + "concreteType": "DeleteComplianceRegistryPayload", + "kind": "LinkedField", + "name": "deleteComplianceRegistry", + "plural": false, + "selections": [ + (v3/*: any*/) + ], + "storageKey": null + } + ], + "type": "Mutation", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [ + (v1/*: any*/), + (v0/*: any*/) + ], + "kind": "Operation", + "name": "ComplianceRegistryGraphDeleteMutation", + "selections": [ + { + "alias": null, + "args": (v2/*: any*/), + "concreteType": "DeleteComplianceRegistryPayload", + "kind": "LinkedField", + "name": "deleteComplianceRegistry", + "plural": false, + "selections": [ + (v3/*: any*/), + { + "alias": null, + "args": null, + "filters": null, + "handle": "deleteEdge", + "key": "", + "kind": "ScalarHandle", + "name": "deletedComplianceRegistryId", + "handleArgs": [ + { + "kind": "Variable", + "name": "connections", + "variableName": "connections" + } + ] + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "4ee4b9ffa6ea477c93a0ba80b8295a44", + "id": null, + "metadata": {}, + "name": "ComplianceRegistryGraphDeleteMutation", + "operationKind": "mutation", + "text": "mutation ComplianceRegistryGraphDeleteMutation(\n $input: DeleteComplianceRegistryInput!\n) {\n deleteComplianceRegistry(input: $input) {\n deletedComplianceRegistryId\n }\n}\n" + } +}; +})(); + +(node as any).hash = "c979ce19185e3d4c20c8a952b5245993"; + +export default node; diff --git a/apps/console/src/hooks/graph/__generated__/ComplianceRegistryGraphListQuery.graphql.ts b/apps/console/src/hooks/graph/__generated__/ComplianceRegistryGraphListQuery.graphql.ts new file mode 100644 index 000000000..63f1aaac4 --- /dev/null +++ b/apps/console/src/hooks/graph/__generated__/ComplianceRegistryGraphListQuery.graphql.ts @@ -0,0 +1,361 @@ +/** + * @generated SignedSource<<8e9c859e7b87a623664e1bd16c620ef1>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type ComplianceRegistryGraphListQuery$variables = { + organizationId: string; +}; +export type ComplianceRegistryGraphListQuery$data = { + readonly node: { + readonly " $fragmentSpreads": FragmentRefs<"ComplianceRegistriesPageFragment">; + }; +}; +export type ComplianceRegistryGraphListQuery = { + response: ComplianceRegistryGraphListQuery$data; + variables: ComplianceRegistryGraphListQuery$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": "__typename", + "storageKey": null +}, +v3 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}, +v4 = [ + { + "kind": "Literal", + "name": "first", + "value": 10 + } +], +v5 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null +}; +return { + "fragment": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Fragment", + "metadata": null, + "name": "ComplianceRegistryGraphListQuery", + "selections": [ + { + "alias": null, + "args": (v1/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + { + "kind": "InlineFragment", + "selections": [ + { + "args": null, + "kind": "FragmentSpread", + "name": "ComplianceRegistriesPageFragment" + } + ], + "type": "Organization", + "abstractKey": null + } + ], + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "ComplianceRegistryGraphListQuery", + "selections": [ + { + "alias": null, + "args": (v1/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v2/*: any*/), + (v3/*: any*/), + { + "kind": "InlineFragment", + "selections": [ + { + "alias": null, + "args": (v4/*: any*/), + "concreteType": "ComplianceRegistryConnection", + "kind": "LinkedField", + "name": "complianceRegistries", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "totalCount", + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "ComplianceRegistryEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "ComplianceRegistry", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v3/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "referenceId", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "area", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "source", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "requirement", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "status", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "lastReviewDate", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "dueDate", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "actionsToBeImplemented", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "regulator", + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "Audit", + "kind": "LinkedField", + "name": "audit", + "plural": false, + "selections": [ + (v3/*: any*/), + (v5/*: any*/), + { + "alias": null, + "args": null, + "concreteType": "Framework", + "kind": "LinkedField", + "name": "framework", + "plural": false, + "selections": [ + (v3/*: any*/), + (v5/*: any*/) + ], + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "People", + "kind": "LinkedField", + "name": "owner", + "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 + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "updatedAt", + "storageKey": null + }, + (v2/*: 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 + } + ], + "storageKey": null + }, + { + "kind": "ClientExtension", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__id", + "storageKey": null + } + ] + } + ], + "storageKey": "complianceRegistries(first:10)" + }, + { + "alias": null, + "args": (v4/*: any*/), + "filters": null, + "handle": "connection", + "key": "ComplianceRegistriesPage_complianceRegistries", + "kind": "LinkedHandle", + "name": "complianceRegistries" + } + ], + "type": "Organization", + "abstractKey": null + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "bec30edcb0f8d7c4147e827c422d524f", + "id": null, + "metadata": {}, + "name": "ComplianceRegistryGraphListQuery", + "operationKind": "query", + "text": "query ComplianceRegistryGraphListQuery(\n $organizationId: ID!\n) {\n node(id: $organizationId) {\n __typename\n ... on Organization {\n ...ComplianceRegistriesPageFragment\n }\n id\n }\n}\n\nfragment ComplianceRegistriesPageFragment on Organization {\n id\n complianceRegistries(first: 10) {\n totalCount\n edges {\n node {\n id\n referenceId\n area\n source\n requirement\n status\n lastReviewDate\n dueDate\n actionsToBeImplemented\n regulator\n audit {\n id\n name\n framework {\n id\n name\n }\n }\n owner {\n id\n fullName\n }\n createdAt\n updatedAt\n __typename\n }\n cursor\n }\n pageInfo {\n hasNextPage\n endCursor\n }\n }\n}\n" + } +}; +})(); + +(node as any).hash = "363434c78eb3e27ac52b6da13c7432a1"; + +export default node; diff --git a/apps/console/src/hooks/graph/__generated__/ComplianceRegistryGraphNodeQuery.graphql.ts b/apps/console/src/hooks/graph/__generated__/ComplianceRegistryGraphNodeQuery.graphql.ts new file mode 100644 index 000000000..e0d9e8f81 --- /dev/null +++ b/apps/console/src/hooks/graph/__generated__/ComplianceRegistryGraphNodeQuery.graphql.ts @@ -0,0 +1,320 @@ +/** + * @generated SignedSource<<14a59a4b5703ae1fb885b70ff8c8d479>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +export type ComplianceRegistryStatus = "CLOSED" | "IN_PROGRESS" | "OPEN"; +export type ComplianceRegistryGraphNodeQuery$variables = { + complianceRegistryId: string; +}; +export type ComplianceRegistryGraphNodeQuery$data = { + readonly node: { + readonly actionsToBeImplemented?: string | null | undefined; + readonly area?: string | null | undefined; + readonly audit?: { + readonly framework: { + readonly id: string; + readonly name: string; + }; + readonly id: string; + readonly name: string | null | undefined; + }; + readonly createdAt?: any; + readonly dueDate?: any | null | undefined; + readonly id?: string; + readonly lastReviewDate?: any | null | undefined; + readonly organization?: { + readonly id: string; + readonly name: string; + }; + readonly owner?: { + readonly fullName: string; + readonly id: string; + }; + readonly referenceId?: string; + readonly regulator?: string | null | undefined; + readonly requirement?: string | null | undefined; + readonly source?: string | null | undefined; + readonly status?: ComplianceRegistryStatus; + readonly updatedAt?: any; + }; +}; +export type ComplianceRegistryGraphNodeQuery = { + response: ComplianceRegistryGraphNodeQuery$data; + variables: ComplianceRegistryGraphNodeQuery$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "complianceRegistryId" + } +], +v1 = [ + { + "kind": "Variable", + "name": "id", + "variableName": "complianceRegistryId" + } +], +v2 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}, +v3 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "referenceId", + "storageKey": null +}, +v4 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "area", + "storageKey": null +}, +v5 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "source", + "storageKey": null +}, +v6 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "requirement", + "storageKey": null +}, +v7 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "actionsToBeImplemented", + "storageKey": null +}, +v8 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "regulator", + "storageKey": null +}, +v9 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "lastReviewDate", + "storageKey": null +}, +v10 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "dueDate", + "storageKey": null +}, +v11 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "status", + "storageKey": null +}, +v12 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null +}, +v13 = [ + (v2/*: any*/), + (v12/*: any*/) +], +v14 = { + "alias": null, + "args": null, + "concreteType": "Audit", + "kind": "LinkedField", + "name": "audit", + "plural": false, + "selections": [ + (v2/*: any*/), + (v12/*: any*/), + { + "alias": null, + "args": null, + "concreteType": "Framework", + "kind": "LinkedField", + "name": "framework", + "plural": false, + "selections": (v13/*: any*/), + "storageKey": null + } + ], + "storageKey": null +}, +v15 = { + "alias": null, + "args": null, + "concreteType": "People", + "kind": "LinkedField", + "name": "owner", + "plural": false, + "selections": [ + (v2/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "fullName", + "storageKey": null + } + ], + "storageKey": null +}, +v16 = { + "alias": null, + "args": null, + "concreteType": "Organization", + "kind": "LinkedField", + "name": "organization", + "plural": false, + "selections": (v13/*: any*/), + "storageKey": null +}, +v17 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "createdAt", + "storageKey": null +}, +v18 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "updatedAt", + "storageKey": null +}; +return { + "fragment": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Fragment", + "metadata": null, + "name": "ComplianceRegistryGraphNodeQuery", + "selections": [ + { + "alias": null, + "args": (v1/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + { + "kind": "InlineFragment", + "selections": [ + (v2/*: any*/), + (v3/*: any*/), + (v4/*: any*/), + (v5/*: any*/), + (v6/*: any*/), + (v7/*: any*/), + (v8/*: any*/), + (v9/*: any*/), + (v10/*: any*/), + (v11/*: any*/), + (v14/*: any*/), + (v15/*: any*/), + (v16/*: any*/), + (v17/*: any*/), + (v18/*: any*/) + ], + "type": "ComplianceRegistry", + "abstractKey": null + } + ], + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "ComplianceRegistryGraphNodeQuery", + "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 + }, + (v2/*: any*/), + { + "kind": "InlineFragment", + "selections": [ + (v3/*: any*/), + (v4/*: any*/), + (v5/*: any*/), + (v6/*: any*/), + (v7/*: any*/), + (v8/*: any*/), + (v9/*: any*/), + (v10/*: any*/), + (v11/*: any*/), + (v14/*: any*/), + (v15/*: any*/), + (v16/*: any*/), + (v17/*: any*/), + (v18/*: any*/) + ], + "type": "ComplianceRegistry", + "abstractKey": null + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "bb1bf89e0916abe7f9807404aa2cf9eb", + "id": null, + "metadata": {}, + "name": "ComplianceRegistryGraphNodeQuery", + "operationKind": "query", + "text": "query ComplianceRegistryGraphNodeQuery(\n $complianceRegistryId: ID!\n) {\n node(id: $complianceRegistryId) {\n __typename\n ... on ComplianceRegistry {\n id\n referenceId\n area\n source\n requirement\n actionsToBeImplemented\n regulator\n lastReviewDate\n dueDate\n status\n audit {\n id\n name\n framework {\n id\n name\n }\n }\n owner {\n id\n fullName\n }\n organization {\n id\n name\n }\n createdAt\n updatedAt\n }\n id\n }\n}\n" + } +}; +})(); + +(node as any).hash = "292fb8d0ee375d8c5e34cbf2633b7f77"; + +export default node; diff --git a/apps/console/src/hooks/graph/__generated__/ComplianceRegistryGraphUpdateMutation.graphql.ts b/apps/console/src/hooks/graph/__generated__/ComplianceRegistryGraphUpdateMutation.graphql.ts new file mode 100644 index 000000000..d5c9aa24a --- /dev/null +++ b/apps/console/src/hooks/graph/__generated__/ComplianceRegistryGraphUpdateMutation.graphql.ts @@ -0,0 +1,262 @@ +/** + * @generated SignedSource<> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +export type ComplianceRegistryStatus = "CLOSED" | "IN_PROGRESS" | "OPEN"; +export type UpdateComplianceRegistryInput = { + actionsToBeImplemented?: string | null | undefined; + area?: string | null | undefined; + auditId?: string | null | undefined; + dueDate?: any | null | undefined; + id: string; + lastReviewDate?: any | null | undefined; + ownerId?: string | null | undefined; + referenceId?: string | null | undefined; + regulator?: string | null | undefined; + requirement?: string | null | undefined; + source?: string | null | undefined; + status?: ComplianceRegistryStatus | null | undefined; +}; +export type ComplianceRegistryGraphUpdateMutation$variables = { + input: UpdateComplianceRegistryInput; +}; +export type ComplianceRegistryGraphUpdateMutation$data = { + readonly updateComplianceRegistry: { + readonly complianceRegistry: { + readonly actionsToBeImplemented: string | null | undefined; + readonly area: string | null | undefined; + readonly audit: { + readonly framework: { + readonly id: string; + readonly name: string; + }; + readonly id: string; + readonly name: string | null | undefined; + }; + readonly dueDate: any | null | undefined; + readonly id: string; + readonly lastReviewDate: any | null | undefined; + readonly owner: { + readonly fullName: string; + readonly id: string; + }; + readonly referenceId: string; + readonly regulator: string | null | undefined; + readonly requirement: string | null | undefined; + readonly source: string | null | undefined; + readonly status: ComplianceRegistryStatus; + readonly updatedAt: any; + }; + }; +}; +export type ComplianceRegistryGraphUpdateMutation = { + response: ComplianceRegistryGraphUpdateMutation$data; + variables: ComplianceRegistryGraphUpdateMutation$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": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null +}, +v3 = [ + { + "alias": null, + "args": [ + { + "kind": "Variable", + "name": "input", + "variableName": "input" + } + ], + "concreteType": "UpdateComplianceRegistryPayload", + "kind": "LinkedField", + "name": "updateComplianceRegistry", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "ComplianceRegistry", + "kind": "LinkedField", + "name": "complianceRegistry", + "plural": false, + "selections": [ + (v1/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "referenceId", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "area", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "source", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "requirement", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "actionsToBeImplemented", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "regulator", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "lastReviewDate", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "dueDate", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "status", + "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 + }, + { + "alias": null, + "args": null, + "concreteType": "Audit", + "kind": "LinkedField", + "name": "audit", + "plural": false, + "selections": [ + (v1/*: any*/), + (v2/*: any*/), + { + "alias": null, + "args": null, + "concreteType": "Framework", + "kind": "LinkedField", + "name": "framework", + "plural": false, + "selections": [ + (v1/*: any*/), + (v2/*: any*/) + ], + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "updatedAt", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } +]; +return { + "fragment": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Fragment", + "metadata": null, + "name": "ComplianceRegistryGraphUpdateMutation", + "selections": (v3/*: any*/), + "type": "Mutation", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "ComplianceRegistryGraphUpdateMutation", + "selections": (v3/*: any*/) + }, + "params": { + "cacheID": "4a4f7e9273f36c87a7e48b5373f69bc2", + "id": null, + "metadata": {}, + "name": "ComplianceRegistryGraphUpdateMutation", + "operationKind": "mutation", + "text": "mutation ComplianceRegistryGraphUpdateMutation(\n $input: UpdateComplianceRegistryInput!\n) {\n updateComplianceRegistry(input: $input) {\n complianceRegistry {\n id\n referenceId\n area\n source\n requirement\n actionsToBeImplemented\n regulator\n lastReviewDate\n dueDate\n status\n owner {\n id\n fullName\n }\n audit {\n id\n name\n framework {\n id\n name\n }\n }\n updatedAt\n }\n }\n}\n" + } +}; +})(); + +(node as any).hash = "c4d17f11888dd8a4769b471797c25b95"; + +export default node; diff --git a/apps/console/src/layouts/MainLayout.tsx b/apps/console/src/layouts/MainLayout.tsx index ee818c7f0..e95a62110 100644 --- a/apps/console/src/layouts/MainLayout.tsx +++ b/apps/console/src/layouts/MainLayout.tsx @@ -5,6 +5,7 @@ import { IconBank, IconBook, IconCircleQuestionmark, + IconCrossLargeX, IconFire3, IconGroup1, IconInboxEmpty, @@ -141,9 +142,14 @@ export function MainLayout() { /> + ; +} + +const complianceRegistriesPageFragment = graphql` + fragment ComplianceRegistriesPageFragment on Organization + @refetchable(queryName: "ComplianceRegistriesPageRefetchQuery") + @argumentDefinitions( + first: { type: "Int", defaultValue: 10 } + after: { type: "CursorKey" } + ) { + id + complianceRegistries(first: $first, after: $after) + @connection(key: "ComplianceRegistriesPage_complianceRegistries") { + __id + totalCount + edges { + node { + id + referenceId + area + source + requirement + status + lastReviewDate + dueDate + actionsToBeImplemented + regulator + audit { + id + name + framework { + id + name + } + } + owner { + id + fullName + } + createdAt + updatedAt + } + } + pageInfo { + hasNextPage + endCursor + } + } + } +`; + +export default function ComplianceRegistriesPage({ queryRef }: ComplianceRegistriesPageProps) { + const { __ } = useTranslate(); + const organizationId = useOrganizationId(); + + usePageTitle(__("Compliance Registries")); + + const organization = usePreloadedQuery( + graphql` + query ComplianceRegistriesPageQuery($organizationId: ID!) { + node(id: $organizationId) { + ... on Organization { + ...ComplianceRegistriesPageFragment + } + } + } + `, + queryRef + ); + + const { data: registriesData, loadNext, hasNext } = usePaginationFragment( + complianceRegistriesPageFragment, + organization.node as ComplianceRegistriesPageFragment$key + ); + + const connectionId = registriesData?.complianceRegistries?.__id || ""; + const registries: ComplianceRegistry[] = registriesData?.complianceRegistries?.edges?.map((edge) => edge.node) ?? []; + + return ( +
+ + + + + + + {registries.length === 0 ? ( + +
+

+ {__("No compliance registry entries yet")} +

+

+ {__("Create your first compliance registry entry to get started.")} +

+
+
+ ) : ( + + + + + + + + + + + + + + + + {registries.map((registry) => ( + + ))} + +
{__("Reference ID")}{__("Area")}{__("Source")}{__("Status")}{__("Audit")}{__("Owner")}{__("Due Date")}{__("Actions")}
+ + {hasNext && ( +
+ +
+ )} +
+ )} +
+ ); +} + +function RegistryRow({ + registry, + connectionId, +}: { + registry: ComplianceRegistry; + connectionId: string; +}) { + const organizationId = useOrganizationId(); + const { __ } = useTranslate(); + const [deleteRegistry] = useMutation(deleteComplianceRegistryMutation); + const confirm = useConfirm(); + + const formatDate = (dateString: string) => { + return new Date(dateString).toLocaleDateString(); + }; + + const handleDelete = () => { + confirm( + () => + promisifyMutation(deleteRegistry)({ + variables: { + input: { + complianceRegistryId: registry.id, + }, + connections: [connectionId], + }, + }), + { + message: sprintf( + __( + "This will permanently delete the compliance registry entry %s. This action cannot be undone." + ), + registry.referenceId + ), + } + ); + }; + + return ( + + + {registry.referenceId} + + {registry.area || "-"} + {registry.source || "-"} + + + {getStatusLabel(registry.status || "OPEN")} + + + + {registry.audit?.name + ? `${registry.audit.framework.name} - ${registry.audit.name}` + : registry.audit?.framework.name || "-" + } + + {registry.owner?.fullName || "-"} + + {registry.dueDate ? ( + + ) : ( + {__("No due date")} + )} + + + + + {__("Delete")} + + + + + ); +} diff --git a/apps/console/src/pages/organizations/complianceRegistries/ComplianceRegistryDetailsPage.tsx b/apps/console/src/pages/organizations/complianceRegistries/ComplianceRegistryDetailsPage.tsx new file mode 100644 index 000000000..3f434a57f --- /dev/null +++ b/apps/console/src/pages/organizations/complianceRegistries/ComplianceRegistryDetailsPage.tsx @@ -0,0 +1,315 @@ +import { + ConnectionHandler, + usePreloadedQuery, + type PreloadedQuery, +} from "react-relay"; +import { + complianceRegistryNodeQuery, + useDeleteComplianceRegistry, + useUpdateComplianceRegistry, + ComplianceRegistriesConnectionKey, +} from "../../../hooks/graph/ComplianceRegistryGraph"; +import { + ActionDropdown, + Badge, + Breadcrumb, + Button, + DropdownItem, + Field, + IconTrashCan, + Option, + Input, + Card, + Textarea, + useToast, + Select, +} from "@probo/ui"; +import { useTranslate } from "@probo/i18n"; +import { useOrganizationId } from "/hooks/useOrganizationId"; +import { PeopleSelectField } from "/components/form/PeopleSelectField"; +import { AuditSelectField } from "/components/form/AuditSelectField"; +import { useFormWithSchema } from "/hooks/useFormWithSchema"; +import { Controller } from "react-hook-form"; +import z from "zod"; +import { getStatusVariant, getStatusLabel, formatDatetime, getComplianceRegistryStatusOptions } from "@probo/helpers"; +import type { ComplianceRegistryGraphNodeQuery } from "/hooks/graph/__generated__/ComplianceRegistryGraphNodeQuery.graphql"; + +const updateRegistrySchema = z.object({ + referenceId: z.string().min(1, "Reference ID is required"), + area: z.string().optional(), + source: z.string().optional(), + requirement: z.string().optional(), + actionsToBeImplemented: z.string().optional(), + regulator: z.string().optional(), + lastReviewDate: z.string().optional(), + dueDate: z.string().optional(), + status: z.enum(["OPEN", "IN_PROGRESS", "CLOSED"]), + ownerId: z.string().min(1, "Owner is required"), + auditId: z.string().min(1, "Audit is required"), +}); + +type Props = { + queryRef: PreloadedQuery; +}; + +export default function ComplianceRegistryDetailsPage(props: Props) { + const data = usePreloadedQuery(complianceRegistryNodeQuery, props.queryRef); + const registry = data.node; + const { __ } = useTranslate(); + const { toast } = useToast(); + const organizationId = useOrganizationId(); + + if (!registry) { + return
{__("Compliance registry entry not found")}
; + } + + const updateRegistry = useUpdateComplianceRegistry(); + const statusOptions = getComplianceRegistryStatusOptions(__); + + const connectionId = ConnectionHandler.getConnectionID( + organizationId, + ComplianceRegistriesConnectionKey + ); + + const deleteRegistry = useDeleteComplianceRegistry({ id: registry.id!, referenceId: registry.referenceId! }, connectionId); + + const { register, handleSubmit, formState, control } = useFormWithSchema( + updateRegistrySchema, + { + defaultValues: { + referenceId: registry.referenceId || "", + area: registry.area || "", + source: registry.source || "", + requirement: registry.requirement || "", + actionsToBeImplemented: registry.actionsToBeImplemented || "", + regulator: registry.regulator || "", + lastReviewDate: registry.lastReviewDate + ? new Date(registry.lastReviewDate).toISOString().split("T")[0] + : "", + dueDate: registry.dueDate + ? new Date(registry.dueDate).toISOString().split("T")[0] + : "", + status: registry.status || "OPEN", + ownerId: registry.owner?.id || "", + auditId: registry.audit?.id || "", + }, + } + ); + + const onSubmit = handleSubmit(async (formData) => { + try { + await updateRegistry({ + id: registry.id!, + referenceId: formData.referenceId, + area: formData.area || undefined, + source: formData.source || undefined, + requirement: formData.requirement || undefined, + actionsToBeImplemented: formData.actionsToBeImplemented || undefined, + regulator: formData.regulator || undefined, + lastReviewDate: formatDatetime(formData.lastReviewDate), + dueDate: formatDatetime(formData.dueDate), + status: formData.status, + ownerId: formData.ownerId, + auditId: formData.auditId, + }); + + toast({ + title: __("Success"), + description: __("Compliance registry entry updated successfully"), + variant: "success", + }); + } catch (error) { + toast({ + title: __("Error"), + description: __("Failed to update compliance registry entry"), + variant: "error", + }); + } + }); + + return ( +
+
+
+ +
+

{registry.referenceId}

+ + {getStatusLabel(registry.status || "OPEN")} + +
+
+ + + + {__("Delete")} + + +
+ + +
+ + + + + ( + + )} + /> + +
+ + + + + + + +
+ +
+ + ( + + )} + /> + {formState.errors.status && ( +

{formState.errors.status.message}

+ )} +
+ + ( + + )} + /> +
+ +
+ + + +
+ +
+ + + + + + + +
+ + +