diff --git a/apps/console/src/components/form/AuditSelectField.tsx b/apps/console/src/components/form/AuditSelectField.tsx new file mode 100644 index 000000000..ebfc4ac8b --- /dev/null +++ b/apps/console/src/components/form/AuditSelectField.tsx @@ -0,0 +1,107 @@ +import { Field, Option, Select, Badge } from "@probo/ui"; +import { Suspense, type ComponentProps } from "react"; +import { useTranslate } from "@probo/i18n"; +import { type Control, Controller, type FieldValues, type Path } from "react-hook-form"; +import { useLazyLoadQuery, graphql } from "react-relay"; +import { getAuditStateVariant, getAuditStateLabel } from "@probo/helpers"; +import type { AuditSelectFieldQuery } from "./__generated__/AuditSelectFieldQuery.graphql"; + +const auditsQuery = graphql` + query AuditSelectFieldQuery($organizationId: ID!) { + organization: node(id: $organizationId) { + ... on Organization { + audits(first: 100) { + edges { + node { + id + name + framework { + id + name + } + state + validFrom + validUntil + } + } + } + } + } + } +`; + +type Props = { + organizationId: string; + control: Control; + name: Path; + label?: string; + error?: string; +} & ComponentProps; + +export function AuditSelectField({ + organizationId, + control, + ...props +}: Props) { + return ( + + } + > + + + + ); +} + +function AuditSelectWithQuery( + props: Pick, "organizationId" | "control" | "name" | "disabled"> +) { + const { __ } = useTranslate(); + const { name, organizationId, control } = props; + const data = useLazyLoadQuery(auditsQuery, { organizationId }); + const audits = data?.organization?.audits?.edges?.map((edge) => edge.node).filter((node) => node !== null) ?? []; + + return ( + ( + + )} + /> + ); +} diff --git a/apps/console/src/components/form/__generated__/AuditSelectFieldQuery.graphql.ts b/apps/console/src/components/form/__generated__/AuditSelectFieldQuery.graphql.ts new file mode 100644 index 000000000..b5ba54d23 --- /dev/null +++ b/apps/console/src/components/form/__generated__/AuditSelectFieldQuery.graphql.ts @@ -0,0 +1,215 @@ +/** + * @generated SignedSource<<21026731cd2ebaf80c8c2e251b9a57f8>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +export type AuditState = "COMPLETED" | "IN_PROGRESS" | "NOT_STARTED" | "OUTDATED" | "REJECTED"; +export type AuditSelectFieldQuery$variables = { + organizationId: string; +}; +export type AuditSelectFieldQuery$data = { + readonly organization: { + readonly audits?: { + readonly edges: ReadonlyArray<{ + readonly node: { + readonly framework: { + readonly id: string; + readonly name: string; + }; + readonly id: string; + readonly name: string | null | undefined; + readonly state: AuditState; + readonly validFrom: any | null | undefined; + readonly validUntil: any | null | undefined; + }; + }>; + }; + }; +}; +export type AuditSelectFieldQuery = { + response: AuditSelectFieldQuery$data; + variables: AuditSelectFieldQuery$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "organizationId" + } +], +v1 = [ + { + "kind": "Variable", + "name": "id", + "variableName": "organizationId" + } +], +v2 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}, +v3 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null +}, +v4 = { + "kind": "InlineFragment", + "selections": [ + { + "alias": null, + "args": [ + { + "kind": "Literal", + "name": "first", + "value": 100 + } + ], + "concreteType": "AuditConnection", + "kind": "LinkedField", + "name": "audits", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "AuditEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Audit", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v2/*: any*/), + (v3/*: any*/), + { + "alias": null, + "args": null, + "concreteType": "Framework", + "kind": "LinkedField", + "name": "framework", + "plural": false, + "selections": [ + (v2/*: any*/), + (v3/*: any*/) + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "state", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "validFrom", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "validUntil", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": "audits(first:100)" + } + ], + "type": "Organization", + "abstractKey": null +}; +return { + "fragment": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Fragment", + "metadata": null, + "name": "AuditSelectFieldQuery", + "selections": [ + { + "alias": "organization", + "args": (v1/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v4/*: any*/) + ], + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "AuditSelectFieldQuery", + "selections": [ + { + "alias": "organization", + "args": (v1/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null + }, + (v4/*: any*/), + (v2/*: any*/) + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "339c5438bb684c44887cc9cb53fb2c6a", + "id": null, + "metadata": {}, + "name": "AuditSelectFieldQuery", + "operationKind": "query", + "text": "query AuditSelectFieldQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n audits(first: 100) {\n edges {\n node {\n id\n name\n framework {\n id\n name\n }\n state\n validFrom\n validUntil\n }\n }\n }\n }\n id\n }\n}\n" + } +}; +})(); + +(node as any).hash = "2bde1a04326144fe1b133672870807eb"; + +export default node; diff --git a/apps/console/src/hooks/graph/NonconformityRegistryGraph.ts b/apps/console/src/hooks/graph/NonconformityRegistryGraph.ts new file mode 100644 index 000000000..fc7f58b5e --- /dev/null +++ b/apps/console/src/hooks/graph/NonconformityRegistryGraph.ts @@ -0,0 +1,243 @@ +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 RegistriesConnectionKey = "RegistriesPage_nonconformityRegistries"; + +export const nonconformityRegistriesQuery = graphql` + query NonconformityRegistryGraphListQuery($organizationId: ID!) { + node(id: $organizationId) { + ... on Organization { + ...RegistriesPageFragment + } + } + } +`; + +export const nonconformityRegistryNodeQuery = graphql` + query NonconformityRegistryGraphNodeQuery($nonconformityRegistryId: ID!) { + node(id: $nonconformityRegistryId) { + ... on NonconformityRegistry { + id + referenceId + description + dateIdentified + rootCause + correctiveAction + dueDate + status + effectivenessCheck + audit { + id + framework { + id + name + } + } + owner { + id + fullName + } + organization { + id + name + } + createdAt + updatedAt + } + } + } +`; + +export const createNonconformityRegistryMutation = graphql` + mutation NonconformityRegistryGraphCreateMutation( + $input: CreateNonconformityRegistryInput! + $connections: [ID!]! + ) { + createNonconformityRegistry(input: $input) { + nonconformityRegistryEdge @prependEdge(connections: $connections) { + node { + id + referenceId + description + status + dateIdentified + dueDate + rootCause + audit { + id + framework { + name + } + } + owner { + id + fullName + } + createdAt + } + } + } + } +`; + +export const updateNonconformityRegistryMutation = graphql` + mutation NonconformityRegistryGraphUpdateMutation($input: UpdateNonconformityRegistryInput!) { + updateNonconformityRegistry(input: $input) { + nonconformityRegistry { + id + referenceId + description + dateIdentified + rootCause + correctiveAction + dueDate + status + effectivenessCheck + owner { + id + fullName + } + audit { + id + framework { + id + name + } + } + updatedAt + } + } + } +`; + +export const deleteNonconformityRegistryMutation = graphql` + mutation NonconformityRegistryGraphDeleteMutation( + $input: DeleteNonconformityRegistryInput! + $connections: [ID!]! + ) { + deleteNonconformityRegistry(input: $input) { + deletedNonconformityRegistryId @deleteEdge(connections: $connections) + } + } +`; + +export const useDeleteNonconformityRegistry = ( + registry: { id: string; referenceId: string }, + connectionId: string +) => { + const { __ } = useTranslate(); + const [mutate] = useMutationWithToasts(deleteNonconformityRegistryMutation, { + successMessage: __("Non conformity registry entry deleted successfully"), + errorMessage: __("Failed to delete non conformity registry entry"), + }); + const confirm = useConfirm(); + + return () => { + confirm( + () => + mutate({ + variables: { + input: { + nonconformityRegistryId: registry.id, + }, + connections: [connectionId], + }, + }), + { + message: sprintf( + __( + "This will permanently delete the non conformity registry entry %s. This action cannot be undone." + ), + registry.referenceId + ), + } + ); + }; +}; + +export const useCreateNonconformityRegistry = (connectionId: string) => { + const [mutate] = useMutation(createNonconformityRegistryMutation); + const { __ } = useTranslate(); + + return (input: { + organizationId: string; + referenceId: string; + description?: string; + auditId: string; + dateIdentified?: string; + rootCause: string; + correctiveAction?: string; + ownerId: string; + dueDate?: string; + status: string; + effectivenessCheck?: string; + }) => { + if (!input.organizationId) { + return alert(__("Failed to create non conformity registry entry: organization is required")); + } + if (!input.referenceId) { + return alert(__("Failed to create non conformity registry entry: reference ID is required")); + } + if (!input.auditId) { + return alert(__("Failed to create non conformity registry entry: audit is required")); + } + if (!input.ownerId) { + return alert(__("Failed to create non conformity registry entry: owner is required")); + } + if (!input.rootCause) { + return alert(__("Failed to create non conformity registry entry: root cause is required")); + } + + return promisifyMutation(mutate)({ + variables: { + input: { + organizationId: input.organizationId, + referenceId: input.referenceId, + description: input.description, + auditId: input.auditId, + dateIdentified: input.dateIdentified, + rootCause: input.rootCause, + correctiveAction: input.correctiveAction, + ownerId: input.ownerId, + dueDate: input.dueDate, + status: input.status || "OPEN", + effectivenessCheck: input.effectivenessCheck, + }, + connections: [connectionId], + }, + }); + }; +}; + +export const useUpdateNonconformityRegistry = () => { + const [mutate] = useMutation(updateNonconformityRegistryMutation); + const { __ } = useTranslate(); + + return (input: { + id: string; + referenceId?: string; + description?: string; + dateIdentified?: string; + rootCause?: string; + correctiveAction?: string; + ownerId?: string; + auditId?: string; + dueDate?: string; + status?: string; + effectivenessCheck?: string; + }) => { + if (!input.id) { + return alert(__("Failed to update non conformity registry entry: registry ID is required")); + } + + return promisifyMutation(mutate)({ + variables: { + input, + }, + }); + }; +}; diff --git a/apps/console/src/hooks/graph/__generated__/NonconformityRegistryGraphCreateMutation.graphql.ts b/apps/console/src/hooks/graph/__generated__/NonconformityRegistryGraphCreateMutation.graphql.ts new file mode 100644 index 000000000..831b63ee5 --- /dev/null +++ b/apps/console/src/hooks/graph/__generated__/NonconformityRegistryGraphCreateMutation.graphql.ts @@ -0,0 +1,348 @@ +/** + * @generated SignedSource<> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +export type NonconformityRegistryStatus = "CLOSED" | "IN_PROGRESS" | "OPEN"; +export type CreateNonconformityRegistryInput = { + auditId: string; + correctiveAction?: string | null | undefined; + dateIdentified?: any | null | undefined; + description?: string | null | undefined; + dueDate?: any | null | undefined; + effectivenessCheck?: string | null | undefined; + organizationId: string; + ownerId: string; + referenceId: string; + rootCause: string; + status: NonconformityRegistryStatus; +}; +export type NonconformityRegistryGraphCreateMutation$variables = { + connections: ReadonlyArray; + input: CreateNonconformityRegistryInput; +}; +export type NonconformityRegistryGraphCreateMutation$data = { + readonly createNonconformityRegistry: { + readonly nonconformityRegistryEdge: { + readonly node: { + readonly audit: { + readonly framework: { + readonly name: string; + }; + readonly id: string; + }; + readonly createdAt: any; + readonly dateIdentified: any | null | undefined; + readonly description: string | null | undefined; + readonly dueDate: any | null | undefined; + readonly id: string; + readonly owner: { + readonly fullName: string; + readonly id: string; + }; + readonly referenceId: string; + readonly rootCause: string; + readonly status: NonconformityRegistryStatus; + }; + }; + }; +}; +export type NonconformityRegistryGraphCreateMutation = { + response: NonconformityRegistryGraphCreateMutation$data; + variables: NonconformityRegistryGraphCreateMutation$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": "description", + "storageKey": null +}, +v6 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "status", + "storageKey": null +}, +v7 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "dateIdentified", + "storageKey": null +}, +v8 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "dueDate", + "storageKey": null +}, +v9 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "rootCause", + "storageKey": null +}, +v10 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null +}, +v11 = { + "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 +}, +v12 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "createdAt", + "storageKey": null +}; +return { + "fragment": { + "argumentDefinitions": [ + (v0/*: any*/), + (v1/*: any*/) + ], + "kind": "Fragment", + "metadata": null, + "name": "NonconformityRegistryGraphCreateMutation", + "selections": [ + { + "alias": null, + "args": (v2/*: any*/), + "concreteType": "CreateNonconformityRegistryPayload", + "kind": "LinkedField", + "name": "createNonconformityRegistry", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "NonconformityRegistryEdge", + "kind": "LinkedField", + "name": "nonconformityRegistryEdge", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "NonconformityRegistry", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v3/*: any*/), + (v4/*: any*/), + (v5/*: any*/), + (v6/*: any*/), + (v7/*: any*/), + (v8/*: any*/), + (v9/*: any*/), + { + "alias": null, + "args": null, + "concreteType": "Audit", + "kind": "LinkedField", + "name": "audit", + "plural": false, + "selections": [ + (v3/*: any*/), + { + "alias": null, + "args": null, + "concreteType": "Framework", + "kind": "LinkedField", + "name": "framework", + "plural": false, + "selections": [ + (v10/*: any*/) + ], + "storageKey": null + } + ], + "storageKey": null + }, + (v11/*: any*/), + (v12/*: any*/) + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "type": "Mutation", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [ + (v1/*: any*/), + (v0/*: any*/) + ], + "kind": "Operation", + "name": "NonconformityRegistryGraphCreateMutation", + "selections": [ + { + "alias": null, + "args": (v2/*: any*/), + "concreteType": "CreateNonconformityRegistryPayload", + "kind": "LinkedField", + "name": "createNonconformityRegistry", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "NonconformityRegistryEdge", + "kind": "LinkedField", + "name": "nonconformityRegistryEdge", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "NonconformityRegistry", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v3/*: any*/), + (v4/*: any*/), + (v5/*: any*/), + (v6/*: any*/), + (v7/*: any*/), + (v8/*: any*/), + (v9/*: any*/), + { + "alias": null, + "args": null, + "concreteType": "Audit", + "kind": "LinkedField", + "name": "audit", + "plural": false, + "selections": [ + (v3/*: any*/), + { + "alias": null, + "args": null, + "concreteType": "Framework", + "kind": "LinkedField", + "name": "framework", + "plural": false, + "selections": [ + (v10/*: any*/), + (v3/*: any*/) + ], + "storageKey": null + } + ], + "storageKey": null + }, + (v11/*: any*/), + (v12/*: any*/) + ], + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "filters": null, + "handle": "prependEdge", + "key": "", + "kind": "LinkedHandle", + "name": "nonconformityRegistryEdge", + "handleArgs": [ + { + "kind": "Variable", + "name": "connections", + "variableName": "connections" + } + ] + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "de251873fde439228e9a1db5928682d3", + "id": null, + "metadata": {}, + "name": "NonconformityRegistryGraphCreateMutation", + "operationKind": "mutation", + "text": "mutation NonconformityRegistryGraphCreateMutation(\n $input: CreateNonconformityRegistryInput!\n) {\n createNonconformityRegistry(input: $input) {\n nonconformityRegistryEdge {\n node {\n id\n referenceId\n description\n status\n dateIdentified\n dueDate\n rootCause\n audit {\n id\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 = "249806dbd8c7c28908461d20b3a0ec52"; + +export default node; diff --git a/apps/console/src/hooks/graph/__generated__/NonconformityRegistryGraphDeleteMutation.graphql.ts b/apps/console/src/hooks/graph/__generated__/NonconformityRegistryGraphDeleteMutation.graphql.ts new file mode 100644 index 000000000..ed37428ce --- /dev/null +++ b/apps/console/src/hooks/graph/__generated__/NonconformityRegistryGraphDeleteMutation.graphql.ts @@ -0,0 +1,132 @@ +/** + * @generated SignedSource<> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +export type DeleteNonconformityRegistryInput = { + nonconformityRegistryId: string; +}; +export type NonconformityRegistryGraphDeleteMutation$variables = { + connections: ReadonlyArray; + input: DeleteNonconformityRegistryInput; +}; +export type NonconformityRegistryGraphDeleteMutation$data = { + readonly deleteNonconformityRegistry: { + readonly deletedNonconformityRegistryId: string; + }; +}; +export type NonconformityRegistryGraphDeleteMutation = { + response: NonconformityRegistryGraphDeleteMutation$data; + variables: NonconformityRegistryGraphDeleteMutation$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": "deletedNonconformityRegistryId", + "storageKey": null +}; +return { + "fragment": { + "argumentDefinitions": [ + (v0/*: any*/), + (v1/*: any*/) + ], + "kind": "Fragment", + "metadata": null, + "name": "NonconformityRegistryGraphDeleteMutation", + "selections": [ + { + "alias": null, + "args": (v2/*: any*/), + "concreteType": "DeleteNonconformityRegistryPayload", + "kind": "LinkedField", + "name": "deleteNonconformityRegistry", + "plural": false, + "selections": [ + (v3/*: any*/) + ], + "storageKey": null + } + ], + "type": "Mutation", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [ + (v1/*: any*/), + (v0/*: any*/) + ], + "kind": "Operation", + "name": "NonconformityRegistryGraphDeleteMutation", + "selections": [ + { + "alias": null, + "args": (v2/*: any*/), + "concreteType": "DeleteNonconformityRegistryPayload", + "kind": "LinkedField", + "name": "deleteNonconformityRegistry", + "plural": false, + "selections": [ + (v3/*: any*/), + { + "alias": null, + "args": null, + "filters": null, + "handle": "deleteEdge", + "key": "", + "kind": "ScalarHandle", + "name": "deletedNonconformityRegistryId", + "handleArgs": [ + { + "kind": "Variable", + "name": "connections", + "variableName": "connections" + } + ] + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "733821706945a20396447b1134615a0e", + "id": null, + "metadata": {}, + "name": "NonconformityRegistryGraphDeleteMutation", + "operationKind": "mutation", + "text": "mutation NonconformityRegistryGraphDeleteMutation(\n $input: DeleteNonconformityRegistryInput!\n) {\n deleteNonconformityRegistry(input: $input) {\n deletedNonconformityRegistryId\n }\n}\n" + } +}; +})(); + +(node as any).hash = "99bc5c0c79ffa8de365ab541c960fc84"; + +export default node; diff --git a/apps/console/src/hooks/graph/__generated__/NonconformityRegistryGraphListQuery.graphql.ts b/apps/console/src/hooks/graph/__generated__/NonconformityRegistryGraphListQuery.graphql.ts new file mode 100644 index 000000000..e1f18091f --- /dev/null +++ b/apps/console/src/hooks/graph/__generated__/NonconformityRegistryGraphListQuery.graphql.ts @@ -0,0 +1,354 @@ +/** + * @generated SignedSource<> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type NonconformityRegistryGraphListQuery$variables = { + organizationId: string; +}; +export type NonconformityRegistryGraphListQuery$data = { + readonly node: { + readonly " $fragmentSpreads": FragmentRefs<"RegistriesPageFragment">; + }; +}; +export type NonconformityRegistryGraphListQuery = { + response: NonconformityRegistryGraphListQuery$data; + variables: NonconformityRegistryGraphListQuery$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": "NonconformityRegistryGraphListQuery", + "selections": [ + { + "alias": null, + "args": (v1/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + { + "kind": "InlineFragment", + "selections": [ + { + "args": null, + "kind": "FragmentSpread", + "name": "RegistriesPageFragment" + } + ], + "type": "Organization", + "abstractKey": null + } + ], + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "NonconformityRegistryGraphListQuery", + "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": "NonconformityRegistryConnection", + "kind": "LinkedField", + "name": "nonconformityRegistries", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "totalCount", + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "NonconformityRegistryEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "NonconformityRegistry", + "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": "description", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "status", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "dateIdentified", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "dueDate", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "rootCause", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "correctiveAction", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "effectivenessCheck", + "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": "nonconformityRegistries(first:10)" + }, + { + "alias": null, + "args": (v4/*: any*/), + "filters": null, + "handle": "connection", + "key": "RegistriesPage_nonconformityRegistries", + "kind": "LinkedHandle", + "name": "nonconformityRegistries" + } + ], + "type": "Organization", + "abstractKey": null + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "c46e924846c9edfbc065055ee875c979", + "id": null, + "metadata": {}, + "name": "NonconformityRegistryGraphListQuery", + "operationKind": "query", + "text": "query NonconformityRegistryGraphListQuery(\n $organizationId: ID!\n) {\n node(id: $organizationId) {\n __typename\n ... on Organization {\n ...RegistriesPageFragment\n }\n id\n }\n}\n\nfragment RegistriesPageFragment on Organization {\n id\n nonconformityRegistries(first: 10) {\n totalCount\n edges {\n node {\n id\n referenceId\n description\n status\n dateIdentified\n dueDate\n rootCause\n correctiveAction\n effectivenessCheck\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 = "eb05763f0b78e8a91640f863bc335378"; + +export default node; diff --git a/apps/console/src/hooks/graph/__generated__/NonconformityRegistryGraphNodeQuery.graphql.ts b/apps/console/src/hooks/graph/__generated__/NonconformityRegistryGraphNodeQuery.graphql.ts new file mode 100644 index 000000000..b21f652a3 --- /dev/null +++ b/apps/console/src/hooks/graph/__generated__/NonconformityRegistryGraphNodeQuery.graphql.ts @@ -0,0 +1,307 @@ +/** + * @generated SignedSource<<7b7982ce6c305e82103ca4422bbb7c2f>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +export type NonconformityRegistryStatus = "CLOSED" | "IN_PROGRESS" | "OPEN"; +export type NonconformityRegistryGraphNodeQuery$variables = { + nonconformityRegistryId: string; +}; +export type NonconformityRegistryGraphNodeQuery$data = { + readonly node: { + readonly audit?: { + readonly framework: { + readonly id: string; + readonly name: string; + }; + readonly id: string; + }; + readonly correctiveAction?: string | null | undefined; + readonly createdAt?: any; + readonly dateIdentified?: any | null | undefined; + readonly description?: string | null | undefined; + readonly dueDate?: any | null | undefined; + readonly effectivenessCheck?: string | null | undefined; + readonly id?: string; + readonly organization?: { + readonly id: string; + readonly name: string; + }; + readonly owner?: { + readonly fullName: string; + readonly id: string; + }; + readonly referenceId?: string; + readonly rootCause?: string; + readonly status?: NonconformityRegistryStatus; + readonly updatedAt?: any; + }; +}; +export type NonconformityRegistryGraphNodeQuery = { + response: NonconformityRegistryGraphNodeQuery$data; + variables: NonconformityRegistryGraphNodeQuery$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "nonconformityRegistryId" + } +], +v1 = [ + { + "kind": "Variable", + "name": "id", + "variableName": "nonconformityRegistryId" + } +], +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": "description", + "storageKey": null +}, +v5 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "dateIdentified", + "storageKey": null +}, +v6 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "rootCause", + "storageKey": null +}, +v7 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "correctiveAction", + "storageKey": null +}, +v8 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "dueDate", + "storageKey": null +}, +v9 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "status", + "storageKey": null +}, +v10 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "effectivenessCheck", + "storageKey": null +}, +v11 = [ + (v2/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null + } +], +v12 = { + "alias": null, + "args": null, + "concreteType": "Audit", + "kind": "LinkedField", + "name": "audit", + "plural": false, + "selections": [ + (v2/*: any*/), + { + "alias": null, + "args": null, + "concreteType": "Framework", + "kind": "LinkedField", + "name": "framework", + "plural": false, + "selections": (v11/*: any*/), + "storageKey": null + } + ], + "storageKey": null +}, +v13 = { + "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 +}, +v14 = { + "alias": null, + "args": null, + "concreteType": "Organization", + "kind": "LinkedField", + "name": "organization", + "plural": false, + "selections": (v11/*: any*/), + "storageKey": null +}, +v15 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "createdAt", + "storageKey": null +}, +v16 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "updatedAt", + "storageKey": null +}; +return { + "fragment": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Fragment", + "metadata": null, + "name": "NonconformityRegistryGraphNodeQuery", + "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*/), + (v12/*: any*/), + (v13/*: any*/), + (v14/*: any*/), + (v15/*: any*/), + (v16/*: any*/) + ], + "type": "NonconformityRegistry", + "abstractKey": null + } + ], + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "NonconformityRegistryGraphNodeQuery", + "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*/), + (v12/*: any*/), + (v13/*: any*/), + (v14/*: any*/), + (v15/*: any*/), + (v16/*: any*/) + ], + "type": "NonconformityRegistry", + "abstractKey": null + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "fd9bf2453d001f33ed1e32ad5784b647", + "id": null, + "metadata": {}, + "name": "NonconformityRegistryGraphNodeQuery", + "operationKind": "query", + "text": "query NonconformityRegistryGraphNodeQuery(\n $nonconformityRegistryId: ID!\n) {\n node(id: $nonconformityRegistryId) {\n __typename\n ... on NonconformityRegistry {\n id\n referenceId\n description\n dateIdentified\n rootCause\n correctiveAction\n dueDate\n status\n effectivenessCheck\n audit {\n id\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 = "3395719bf2203fba19ccc94d6f740014"; + +export default node; diff --git a/apps/console/src/hooks/graph/__generated__/NonconformityRegistryGraphUpdateMutation.graphql.ts b/apps/console/src/hooks/graph/__generated__/NonconformityRegistryGraphUpdateMutation.graphql.ts new file mode 100644 index 000000000..776fefdab --- /dev/null +++ b/apps/console/src/hooks/graph/__generated__/NonconformityRegistryGraphUpdateMutation.graphql.ts @@ -0,0 +1,250 @@ +/** + * @generated SignedSource<<19732d1bd6a2a118a8e813058693bad0>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +export type NonconformityRegistryStatus = "CLOSED" | "IN_PROGRESS" | "OPEN"; +export type UpdateNonconformityRegistryInput = { + auditId?: string | null | undefined; + correctiveAction?: string | null | undefined; + dateIdentified?: any | null | undefined; + description?: string | null | undefined; + dueDate?: any | null | undefined; + effectivenessCheck?: string | null | undefined; + id: string; + ownerId?: string | null | undefined; + referenceId?: string | null | undefined; + rootCause?: string | null | undefined; + status?: NonconformityRegistryStatus | null | undefined; +}; +export type NonconformityRegistryGraphUpdateMutation$variables = { + input: UpdateNonconformityRegistryInput; +}; +export type NonconformityRegistryGraphUpdateMutation$data = { + readonly updateNonconformityRegistry: { + readonly nonconformityRegistry: { + readonly audit: { + readonly framework: { + readonly id: string; + readonly name: string; + }; + readonly id: string; + }; + readonly correctiveAction: string | null | undefined; + readonly dateIdentified: any | null | undefined; + readonly description: string | null | undefined; + readonly dueDate: any | null | undefined; + readonly effectivenessCheck: string | null | undefined; + readonly id: string; + readonly owner: { + readonly fullName: string; + readonly id: string; + }; + readonly referenceId: string; + readonly rootCause: string; + readonly status: NonconformityRegistryStatus; + readonly updatedAt: any; + }; + }; +}; +export type NonconformityRegistryGraphUpdateMutation = { + response: NonconformityRegistryGraphUpdateMutation$data; + variables: NonconformityRegistryGraphUpdateMutation$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "input" + } +], +v1 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}, +v2 = [ + { + "alias": null, + "args": [ + { + "kind": "Variable", + "name": "input", + "variableName": "input" + } + ], + "concreteType": "UpdateNonconformityRegistryPayload", + "kind": "LinkedField", + "name": "updateNonconformityRegistry", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "NonconformityRegistry", + "kind": "LinkedField", + "name": "nonconformityRegistry", + "plural": false, + "selections": [ + (v1/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "referenceId", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "description", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "dateIdentified", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "rootCause", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "correctiveAction", + "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, + "kind": "ScalarField", + "name": "effectivenessCheck", + "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*/), + { + "alias": null, + "args": null, + "concreteType": "Framework", + "kind": "LinkedField", + "name": "framework", + "plural": false, + "selections": [ + (v1/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null + } + ], + "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": "NonconformityRegistryGraphUpdateMutation", + "selections": (v2/*: any*/), + "type": "Mutation", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "NonconformityRegistryGraphUpdateMutation", + "selections": (v2/*: any*/) + }, + "params": { + "cacheID": "a76545f5642f914e8e8206160e1d8839", + "id": null, + "metadata": {}, + "name": "NonconformityRegistryGraphUpdateMutation", + "operationKind": "mutation", + "text": "mutation NonconformityRegistryGraphUpdateMutation(\n $input: UpdateNonconformityRegistryInput!\n) {\n updateNonconformityRegistry(input: $input) {\n nonconformityRegistry {\n id\n referenceId\n description\n dateIdentified\n rootCause\n correctiveAction\n dueDate\n status\n effectivenessCheck\n owner {\n id\n fullName\n }\n audit {\n id\n framework {\n id\n name\n }\n }\n updatedAt\n }\n }\n}\n" + } +}; +})(); + +(node as any).hash = "456b4f3b6f36c0b518bd3eb870e0d2c5"; + +export default node; diff --git a/apps/console/src/layouts/MainLayout.tsx b/apps/console/src/layouts/MainLayout.tsx index fc399afed..ee818c7f0 100644 --- a/apps/console/src/layouts/MainLayout.tsx +++ b/apps/console/src/layouts/MainLayout.tsx @@ -3,6 +3,7 @@ import { DropdownSeparator, IconArrowBoxLeft, IconBank, + IconBook, IconCircleQuestionmark, IconFire3, IconGroup1, @@ -138,6 +139,11 @@ export function MainLayout() { icon={IconCheckmark1} to={`${prefix}/audits`} /> + [s.signedBy.id, s])); const organizationId = useOrganizationId(); - const people = usePeople(organizationId); + const people = usePeople(organizationId, { excludeContractEnded: true }); const signable = props.version.status === "PUBLISHED"; if (!props.version.signatures) { diff --git a/apps/console/src/pages/organizations/nonconformityRegistries/NonconformityRegistriesPage.tsx b/apps/console/src/pages/organizations/nonconformityRegistries/NonconformityRegistriesPage.tsx new file mode 100644 index 000000000..eebc93d1a --- /dev/null +++ b/apps/console/src/pages/organizations/nonconformityRegistries/NonconformityRegistriesPage.tsx @@ -0,0 +1,273 @@ +import { + Button, + IconPlusLarge, + PageHeader, + Card, + Thead, + Tbody, + Tr, + Th, + Td, + Badge, + ActionDropdown, + DropdownItem, + IconTrashCan, + Table, + useConfirm, +} from "@probo/ui"; +import { useTranslate } from "@probo/i18n"; +import { usePageTitle } from "@probo/hooks"; +import { + graphql, + usePaginationFragment, + usePreloadedQuery, + useMutation, + ConnectionHandler, + type PreloadedQuery, +} from "react-relay"; +import { useOrganizationId } from "/hooks/useOrganizationId"; +import { CreateNonconformityRegistryDialog } from "./dialogs/CreateNonconformityRegistryDialog"; +import { deleteNonconformityRegistryMutation, RegistriesConnectionKey } from "../../../hooks/graph/NonconformityRegistryGraph"; +import { sprintf, promisifyMutation, getStatusVariant, getStatusLabel } from "@probo/helpers"; +import type { RegistriesPageQuery } from "./__generated__/RegistriesPageQuery.graphql"; +import type { + RegistriesPageFragment$key, + RegistriesPageFragment$data, +} from "./__generated__/RegistriesPageFragment.graphql"; + +type NonconformityRegistry = RegistriesPageFragment$data['nonconformityRegistries']['edges'][number]['node']; + +interface NonconformityRegistriesPageProps { + queryRef: PreloadedQuery; +} + +const registriesPageFragment = graphql` + fragment RegistriesPageFragment on Organization + @refetchable(queryName: "RegistriesPageRefetchQuery") + @argumentDefinitions( + first: { type: "Int", defaultValue: 10 } + after: { type: "CursorKey" } + ) { + id + nonconformityRegistries(first: $first, after: $after) + @connection(key: "RegistriesPage_nonconformityRegistries") { + __id + totalCount + edges { + node { + id + referenceId + description + status + dateIdentified + dueDate + rootCause + correctiveAction + effectivenessCheck + audit { + id + name + framework { + id + name + } + } + owner { + id + fullName + } + createdAt + updatedAt + } + } + pageInfo { + hasNextPage + endCursor + } + } + } +`; + +export default function NonconformityRegistriesPage({ queryRef }: NonconformityRegistriesPageProps) { + const { __ } = useTranslate(); + const organizationId = useOrganizationId(); + + usePageTitle(__("Nonconformity Registries")); + + const organization = usePreloadedQuery( + graphql` + query RegistriesPageQuery($organizationId: ID!) { + node(id: $organizationId) { + ... on Organization { + ...RegistriesPageFragment + } + } + } + `, + queryRef + ); + + const { data: registriesData, loadNext, hasNext } = usePaginationFragment( + registriesPageFragment, + organization.node as RegistriesPageFragment$key + ); + + const connectionId = ConnectionHandler.getConnectionID(organizationId, RegistriesConnectionKey); + const registries: NonconformityRegistry[] = registriesData?.nonconformityRegistries?.edges?.map((edge) => edge.node) ?? []; + + return ( +
+ + + + + + + {registries.length === 0 ? ( + +
+

+ {__("No non conformity registry entries yet")} +

+

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

+
+
+ ) : ( + + + + + + + + + + + + + + + {registries.map((registry) => ( + + ))} + +
{__("Reference ID")}{__("Description")}{__("Status")}{__("Audit")}{__("Owner")}{__("Due Date")}{__("Actions")}
+ + {hasNext && ( +
+ +
+ )} +
+ )} + + +
+ ); +} + +function RegistryRow({ + registry, + connectionId, +}: { + registry: NonconformityRegistry; + connectionId: string; +}) { + const organizationId = useOrganizationId(); + const { __ } = useTranslate(); + const confirm = useConfirm(); + const [deleteRegistry] = useMutation(deleteNonconformityRegistryMutation); + + const formatDate = (dateString: string) => { + return new Date(dateString).toLocaleDateString(); + }; + + const handleDeleteRegistry = (registry: NonconformityRegistry) => { + if (!connectionId) return; + + confirm( + () => { + return promisifyMutation(deleteRegistry)({ + variables: { + input: { + nonconformityRegistryId: registry.id, + }, + connections: [connectionId], + }, + }); + }, + { + message: sprintf( + __( + "This will permanently delete the registry entry %s. This action cannot be undone." + ), + registry.referenceId + ), + } + ); + }; + + return ( + + + {registry.referenceId} + + +
+

+ {registry.description || __("No description")} +

+
+ + + + {getStatusLabel(registry.status)} + + + + {registry.audit.name + ? `${registry.audit.framework.name} - ${registry.audit.name}` + : registry.audit.framework.name + } + + {registry.owner.fullName} + + {registry.dueDate ? ( + + ) : ( + {__("No due date")} + )} + + + + handleDeleteRegistry(registry)} + > + {__("Delete")} + + + + + ); +} diff --git a/apps/console/src/pages/organizations/nonconformityRegistries/NonconformityRegistryDetailsPage.tsx b/apps/console/src/pages/organizations/nonconformityRegistries/NonconformityRegistryDetailsPage.tsx new file mode 100644 index 000000000..f41adf530 --- /dev/null +++ b/apps/console/src/pages/organizations/nonconformityRegistries/NonconformityRegistryDetailsPage.tsx @@ -0,0 +1,264 @@ +import { + ConnectionHandler, + usePreloadedQuery, + type PreloadedQuery, +} from "react-relay"; +import { + nonconformityRegistryNodeQuery, + useDeleteNonconformityRegistry, + useUpdateNonconformityRegistry, + RegistriesConnectionKey, +} from "../../../hooks/graph/NonconformityRegistryGraph"; +import { + ActionDropdown, + Badge, + Breadcrumb, + Button, + DropdownItem, + Field, + IconTrashCan, + Option, + Input, + Card, + Textarea, + useToast, +} from "@probo/ui"; +import { useTranslate } from "@probo/i18n"; +import { useOrganizationId } from "/hooks/useOrganizationId"; +import { ControlledField } from "/components/form/ControlledField"; +import { PeopleSelectField } from "/components/form/PeopleSelectField"; +import { AuditSelectField } from "/components/form/AuditSelectField"; +import { useFormWithSchema } from "/hooks/useFormWithSchema"; +import z from "zod"; +import { getStatusVariant, getStatusLabel, formatDatetime, getNonconformityRegistryStatusOptions } from "@probo/helpers"; +import type { NonconformityRegistryGraphNodeQuery } from "/hooks/graph/__generated__/NonconformityRegistryGraphNodeQuery.graphql"; + +const updateRegistrySchema = z.object({ + referenceId: z.string().min(1, "Reference ID is required"), + description: z.string().optional(), + dateIdentified: z.string().optional(), + dueDate: z.string().optional(), + rootCause: z.string().min(1, "Root cause is required"), + correctiveAction: z.string().optional(), + effectivenessCheck: 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 NonconformityRegistryDetailsPage(props: Props) { + const data = usePreloadedQuery( + nonconformityRegistryNodeQuery, + props.queryRef + ); + const registry = data.node; + const { __ } = useTranslate(); + const organizationId = useOrganizationId(); + + const deleteRegistry = useDeleteNonconformityRegistry( + { id: registry.id!, referenceId: registry.referenceId! }, + ConnectionHandler.getConnectionID(organizationId, RegistriesConnectionKey) + ); + + const { control, formState, handleSubmit, register, reset } = useFormWithSchema( + updateRegistrySchema, + { + defaultValues: { + referenceId: registry.referenceId || "", + description: registry.description || "", + dateIdentified: registry.dateIdentified?.split('T')[0] || "", + dueDate: registry.dueDate?.split('T')[0] || "", + rootCause: registry.rootCause || "", + correctiveAction: registry.correctiveAction || "", + effectivenessCheck: registry.effectivenessCheck || "", + status: registry.status || "OPEN", + ownerId: registry.owner?.id || "", + auditId: registry.audit?.id || "", + }, + } + ); + + const updateRegistry = useUpdateNonconformityRegistry(); + const { toast } = useToast(); + + const onSubmit = handleSubmit(async (formData) => { + if (!registry.id) return; + + try { + await updateRegistry({ + id: registry.id, + referenceId: formData.referenceId, + description: formData.description, + dateIdentified: formatDatetime(formData.dateIdentified), + dueDate: formatDatetime(formData.dueDate), + rootCause: formData.rootCause, + correctiveAction: formData.correctiveAction, + effectivenessCheck: formData.effectivenessCheck, + status: formData.status, + ownerId: formData.ownerId, + auditId: formData.auditId, + }); + reset(formData); + toast({ + title: __("Success"), + description: __("Non conformity registry entry updated successfully"), + variant: "success", + }); + } catch (error) { + toast({ + title: __("Error"), + description: error instanceof Error ? error.message : __("Failed to update non conformity registry entry"), + variant: "error", + }); + } + }); + + const statusOptions = getNonconformityRegistryStatusOptions(__); + + return ( +
+ + +
+
+
+ {registry.referenceId} +
+ + {getStatusLabel(registry.status || "OPEN")} + +
+ + + {__("Delete")} + + +
+ +
+ +
+

{__("Nonconformity Registry")}

+ + + + + + + + +