diff --git a/apps/console/src/components/form/AuditSelectField.tsx b/apps/console/src/components/form/AuditSelectField.tsx index 0303fe788..3e93a1a2f 100644 --- a/apps/console/src/components/form/AuditSelectField.tsx +++ b/apps/console/src/components/form/AuditSelectField.tsx @@ -105,8 +105,8 @@ function AuditSelectWithQuery(
{audit.name - ? `${audit.framework.name} - ${audit.name}` - : audit.framework.name} + ? `${audit.framework?.name} - ${audit.name}` + : audit.framework?.name}
diff --git a/apps/console/src/components/risks/LinkedRisksDialog.tsx b/apps/console/src/components/risks/LinkedRisksDialog.tsx index 8582ae91d..94b7055f9 100644 --- a/apps/console/src/components/risks/LinkedRisksDialog.tsx +++ b/apps/console/src/components/risks/LinkedRisksDialog.tsx @@ -136,6 +136,7 @@ type RowProps = { name: string; category: string; id: string; + description?: string | null; }; linkedRisks: Set; disabled?: boolean; diff --git a/apps/console/src/hooks/graph/AuditGraph.ts b/apps/console/src/hooks/graph/AuditGraph.ts index 1936c91b3..5a554d234 100644 --- a/apps/console/src/hooks/graph/AuditGraph.ts +++ b/apps/console/src/hooks/graph/AuditGraph.ts @@ -121,7 +121,7 @@ export const deleteAuditMutation = graphql` `; export const useDeleteAudit = ( - audit: { id: string; framework: { name: string } }, + audit: { id: string; framework?: { name: string } | null }, connectionId: string, onSuccess?: () => void, ) => { @@ -150,7 +150,7 @@ export const useDeleteAudit = ( __( "This will permanently delete the audit for %s. This action cannot be undone.", ), - audit.framework.name, + audit.framework?.name ?? "", ), }, ); diff --git a/apps/console/src/hooks/graph/ContinualImprovementGraph.ts b/apps/console/src/hooks/graph/ContinualImprovementGraph.ts deleted file mode 100644 index cb502df9c..000000000 --- a/apps/console/src/hooks/graph/ContinualImprovementGraph.ts +++ /dev/null @@ -1,232 +0,0 @@ -import { promisifyMutation, sprintf } from "@probo/helpers"; -import { useTranslate } from "@probo/i18n"; -import { useConfirm } from "@probo/ui"; -import { useMutation } from "react-relay"; -import { graphql } from "relay-runtime"; - -import { useMutationWithToasts } from "../useMutationWithToasts"; - -/* eslint-disable relay/unused-fields, relay/must-colocate-fragment-spreads */ - -export const ContinualImprovementsConnectionKey - = "ContinualImprovementsPage_continualImprovements"; - -export const continualImprovementsQuery = graphql` - query ContinualImprovementGraphListQuery( - $organizationId: ID! - $snapshotId: ID - ) { - node(id: $organizationId) { - ... on Organization { - canCreateContinualImprovement: permission( - action: "core:continual-improvement:create" - ) - ...ContinualImprovementsPageFragment @arguments(snapshotId: $snapshotId) - } - } - } -`; - -export const continualImprovementNodeQuery = graphql` - query ContinualImprovementGraphNodeQuery($continualImprovementId: ID!) { - node(id: $continualImprovementId) { - ... on ContinualImprovement { - id - snapshotId - sourceId - referenceId - description - source - targetDate - status - priority - owner { - id - fullName - } - organization { - id - name - } - createdAt - updatedAt - canUpdate: permission(action: "core:continual-improvement:update") - canDelete: permission(action: "core:continual-improvement:delete") - } - } - } -`; - -export const createContinualImprovementMutation = graphql` - mutation ContinualImprovementGraphCreateMutation( - $input: CreateContinualImprovementInput! - $connections: [ID!]! - ) { - createContinualImprovement(input: $input) { - continualImprovementEdge @prependEdge(connections: $connections) { - node { - id - referenceId - description - source - targetDate - status - priority - owner { - id - fullName - } - createdAt - canUpdate: permission(action: "core:continual-improvement:update") - canDelete: permission(action: "core:continual-improvement:delete") - } - } - } - } -`; - -export const updateContinualImprovementMutation = graphql` - mutation ContinualImprovementGraphUpdateMutation( - $input: UpdateContinualImprovementInput! - ) { - updateContinualImprovement(input: $input) { - continualImprovement { - id - referenceId - description - source - targetDate - status - priority - owner { - id - fullName - } - updatedAt - } - } - } -`; - -export const deleteContinualImprovementMutation = graphql` - mutation ContinualImprovementGraphDeleteMutation( - $input: DeleteContinualImprovementInput! - $connections: [ID!]! - ) { - deleteContinualImprovement(input: $input) { - deletedContinualImprovementId @deleteEdge(connections: $connections) - } - } -`; - -export const useDeleteContinualImprovement = ( - improvement: { id: string; referenceId: string }, - connectionId: string, -) => { - const { __ } = useTranslate(); - const [mutate] = useMutationWithToasts(deleteContinualImprovementMutation, { - successMessage: __("Continual improvement deleted successfully"), - errorMessage: __("Failed to delete continual improvement"), - }); - const confirm = useConfirm(); - - return () => { - confirm( - () => - mutate({ - variables: { - input: { - continualImprovementId: improvement.id, - }, - connections: [connectionId], - }, - }), - { - message: sprintf( - __( - "This will permanently delete the continual improvement %s. This action cannot be undone.", - ), - improvement.referenceId, - ), - }, - ); - }; -}; - -export const useCreateContinualImprovement = (connectionId: string) => { - // eslint-disable-next-line relay/generated-typescript-types - const [mutate] = useMutation(createContinualImprovementMutation); - const { __ } = useTranslate(); - - return (input: { - organizationId: string; - referenceId: string; - description?: string; - source?: string; - ownerId: string; - targetDate?: string; - status: string; - priority: string; - }) => { - if (!input.organizationId) { - return alert( - __("Failed to create continual improvement: organization is required"), - ); - } - if (!input.referenceId) { - return alert( - __("Failed to create continual improvement: reference ID is required"), - ); - } - if (!input.ownerId) { - return alert( - __("Failed to create continual improvement: owner is required"), - ); - } - - return promisifyMutation(mutate)({ - variables: { - input: { - organizationId: input.organizationId, - referenceId: input.referenceId, - description: input.description, - source: input.source, - ownerId: input.ownerId, - targetDate: input.targetDate, - status: input.status || "OPEN", - priority: input.priority || "MEDIUM", - }, - connections: [connectionId], - }, - }); - }; -}; - -export const useUpdateContinualImprovement = () => { - // eslint-disable-next-line relay/generated-typescript-types - const [mutate] = useMutation(updateContinualImprovementMutation); - const { __ } = useTranslate(); - - return (input: { - id: string; - referenceId?: string; - description?: string; - source?: string; - ownerId?: string; - targetDate?: string | null; - status?: string; - priority?: string; - }) => { - if (!input.id) { - return alert( - __("Failed to update continual improvement: ID is required"), - ); - } - - return promisifyMutation(mutate)({ - variables: { - input, - }, - }); - }; -}; diff --git a/apps/console/src/hooks/graph/NonconformityGraph.ts b/apps/console/src/hooks/graph/NonconformityGraph.ts deleted file mode 100644 index c40c39de6..000000000 --- a/apps/console/src/hooks/graph/NonconformityGraph.ts +++ /dev/null @@ -1,261 +0,0 @@ -import { promisifyMutation, sprintf } from "@probo/helpers"; -import { useTranslate } from "@probo/i18n"; -import { useConfirm } from "@probo/ui"; -import { useMutation } from "react-relay"; -import { graphql } from "relay-runtime"; - -import { useMutationWithToasts } from "../useMutationWithToasts"; - -/* eslint-disable relay/unused-fields, relay/must-colocate-fragment-spreads */ - -export const NonconformitiesConnectionKey - = "NonconformitiesPage_nonconformities"; - -export const nonconformitiesQuery = graphql` - query NonconformityGraphListQuery($organizationId: ID!, $snapshotId: ID) { - node(id: $organizationId) { - ... on Organization { - canCreateNonconformity: permission(action: "core:nonconformity:create") - ...NonconformitiesPageFragment @arguments(snapshotId: $snapshotId) - } - } - } -`; - -export const nonconformityNodeQuery = graphql` - query NonconformityGraphNodeQuery($nonconformityId: ID!) { - node(id: $nonconformityId) { - ... on Nonconformity { - id - snapshotId - referenceId - description - dateIdentified - rootCause - correctiveAction - dueDate - status - effectivenessCheck - audit { - id - framework { - id - name - } - } - owner { - id - fullName - } - organization { - id - name - } - createdAt - updatedAt - canUpdate: permission(action: "core:nonconformity:update") - canDelete: permission(action: "core:nonconformity:delete") - } - } - } -`; - -export const createNonconformityMutation = graphql` - mutation NonconformityGraphCreateMutation( - $input: CreateNonconformityInput! - $connections: [ID!]! - ) { - createNonconformity(input: $input) { - nonconformityEdge @prependEdge(connections: $connections) { - node { - id - referenceId - description - status - dateIdentified - dueDate - rootCause - audit { - id - framework { - name - } - } - owner { - id - fullName - } - createdAt - canUpdate: permission(action: "core:nonconformity:update") - canDelete: permission(action: "core:nonconformity:delete") - } - } - } - } -`; - -export const updateNonconformityMutation = graphql` - mutation NonconformityGraphUpdateMutation($input: UpdateNonconformityInput!) { - updateNonconformity(input: $input) { - nonconformity { - id - referenceId - description - dateIdentified - rootCause - correctiveAction - dueDate - status - effectivenessCheck - owner { - id - fullName - } - audit { - id - framework { - id - name - } - } - updatedAt - } - } - } -`; - -export const deleteNonconformityMutation = graphql` - mutation NonconformityGraphDeleteMutation( - $input: DeleteNonconformityInput! - $connections: [ID!]! - ) { - deleteNonconformity(input: $input) { - deletedNonconformityId @deleteEdge(connections: $connections) - } - } -`; - -export const useDeleteNonconformity = ( - nonconformity: { id: string; referenceId: string }, - connectionId: string, -) => { - const { __ } = useTranslate(); - const [mutate] = useMutationWithToasts(deleteNonconformityMutation, { - successMessage: __("Nonconformity deleted successfully"), - errorMessage: __("Failed to delete nonconformity"), - }); - const confirm = useConfirm(); - - return () => { - confirm( - () => - mutate({ - variables: { - input: { - nonconformityId: nonconformity.id, - }, - connections: [connectionId], - }, - }), - { - message: sprintf( - __( - "This will permanently delete the nonconformity %s. This action cannot be undone.", - ), - nonconformity.referenceId, - ), - }, - ); - }; -}; - -export const useCreateNonconformity = (connectionId: string) => { - // eslint-disable-next-line relay/generated-typescript-types - const [mutate] = useMutation(createNonconformityMutation); - 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 nonconformity: organization is required"), - ); - } - if (!input.referenceId) { - return alert( - __("Failed to create nonconformity: reference ID is required"), - ); - } - if (!input.ownerId) { - return alert(__("Failed to create nonconformity: owner is required")); - } - if (!input.rootCause) { - return alert( - __("Failed to create nonconformity: root cause is required"), - ); - } - - return promisifyMutation(mutate)({ - variables: { - input: { - organizationId: input.organizationId, - referenceId: input.referenceId, - description: input.description, - auditId: input.auditId || undefined, - 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 useUpdateNonconformity = () => { - // eslint-disable-next-line relay/generated-typescript-types - const [mutate] = useMutation(updateNonconformityMutation); - const { __ } = useTranslate(); - - return (input: { - id: string; - referenceId?: string; - description?: string; - dateIdentified?: string | null; - rootCause?: string; - correctiveAction?: string; - ownerId?: string; - auditId?: string | null; - dueDate?: string | null; - status?: string; - effectivenessCheck?: string; - }) => { - if (!input.id) { - return alert(__("Failed to update nonconformity: ID is required")); - } - - return promisifyMutation(mutate)({ - variables: { - input: { - ...input, - auditId: input.auditId || null, - }, - }, - }); - }; -}; diff --git a/apps/console/src/pages/iam/organizations/_components/Sidebar.tsx b/apps/console/src/pages/iam/organizations/_components/Sidebar.tsx index a5f280c17..6406483e1 100644 --- a/apps/console/src/pages/iam/organizations/_components/Sidebar.tsx +++ b/apps/console/src/pages/iam/organizations/_components/Sidebar.tsx @@ -6,16 +6,15 @@ import { IconCalendar1, IconCircleProgress, IconClock, - IconCrossLargeX, IconFire3, IconGroup1, IconInboxEmpty, IconListStack, IconLock, + IconMagnifyingGlass, IconMedal, IconPageCheck, IconPageTextLine, - IconRotateCw, IconSettingsGear2, IconShield, IconStore, @@ -41,11 +40,8 @@ const fragment = graphql` canListAssets: permission(action: "core:asset:list") canListData: permission(action: "core:datum:list") canListAudits: permission(action: "core:audit:list") - canListNonconformities: permission(action: "core:nonconformity:list") + canListFindings: permission(action: "core:finding:list") canListObligations: permission(action: "core:obligation:list") - canListContinualImprovements: permission( - action: "core:continual-improvement:list" - ) canListProcessingActivities: permission( action: "core:processing-activity:list" ) @@ -148,11 +144,11 @@ export function Sidebar(props: { fKey: SidebarFragment$key }) { to={`${prefix}/audits`} /> )} - {organization.canListNonconformities && ( + {organization.canListFindings && ( )} {organization.canListObligations && ( @@ -162,13 +158,6 @@ export function Sidebar(props: { fKey: SidebarFragment$key }) { to={`${prefix}/obligations`} /> )} - {organization.canListContinualImprovements && ( - - )} {organization.canListProcessingActivities && ( -
{audit.framework.name}
+
{audit.framework?.name}
{audit.name || __("Untitled")} {validUntilFormatted} diff --git a/apps/console/src/pages/organizations/continualImprovements/ContinualImprovementDetailsPage.tsx b/apps/console/src/pages/organizations/continualImprovements/ContinualImprovementDetailsPage.tsx deleted file mode 100644 index 95dcb600e..000000000 --- a/apps/console/src/pages/organizations/continualImprovements/ContinualImprovementDetailsPage.tsx +++ /dev/null @@ -1,339 +0,0 @@ -import { formatError, type GraphQLError } from "@probo/helpers"; -import { - formatDatetime, - getStatusLabel, - getStatusVariant, - validateSnapshotConsistency, -} from "@probo/helpers"; -import { useTranslate } from "@probo/i18n"; -import { - ActionDropdown, - Badge, - Breadcrumb, - Button, - Card, - DropdownItem, - Field, - Input, - Label, - Option, - Select, - Textarea, - useToast, -} from "@probo/ui"; -import { Controller } from "react-hook-form"; -import { - ConnectionHandler, - type PreloadedQuery, - usePreloadedQuery, -} from "react-relay"; -import { useParams } from "react-router"; -import { z } from "zod"; - -import type { ContinualImprovementGraphNodeQuery } from "#/__generated__/core/ContinualImprovementGraphNodeQuery.graphql"; -import { PeopleSelectField } from "#/components/form/PeopleSelectField"; -import { SnapshotBanner } from "#/components/SnapshotBanner"; -import { useFormWithSchema } from "#/hooks/useFormWithSchema"; -import { useOrganizationId } from "#/hooks/useOrganizationId"; - -import { - continualImprovementNodeQuery, - ContinualImprovementsConnectionKey, - useDeleteContinualImprovement, - useUpdateContinualImprovement, -} from "../../../hooks/graph/ContinualImprovementGraph"; - -const updateImprovementSchema = z.object({ - referenceId: z.string().min(1, "Reference ID is required"), - description: z.string().optional(), - source: z.string().optional(), - targetDate: z.string().optional(), - status: z.enum(["OPEN", "IN_PROGRESS", "CLOSED"]), - priority: z.enum(["LOW", "MEDIUM", "HIGH"]), - ownerId: z.string().min(1, "Owner is required"), -}); - -type Props = { - queryRef: PreloadedQuery; -}; - -export default function ContinualImprovementDetailsPage(props: Props) { - const { node: improvement } - = usePreloadedQuery( - continualImprovementNodeQuery, - props.queryRef, - ); - const { __ } = useTranslate(); - const { toast } = useToast(); - const organizationId = useOrganizationId(); - const { snapshotId } = useParams<{ snapshotId?: string }>(); - const isSnapshotMode = Boolean(snapshotId); - - validateSnapshotConsistency(improvement, snapshotId); - - const updateImprovement = useUpdateContinualImprovement(); - - const connectionId = ConnectionHandler.getConnectionID( - organizationId, - ContinualImprovementsConnectionKey, - { filter: { snapshotId: snapshotId || null } }, - ); - - const deleteImprovement = useDeleteContinualImprovement( - { id: improvement.id!, referenceId: improvement.referenceId! }, - connectionId, - ); - - const { register, handleSubmit, formState, control } = useFormWithSchema( - updateImprovementSchema, - { - defaultValues: { - referenceId: improvement.referenceId || "", - description: improvement.description || "", - source: improvement.source || "", - targetDate: improvement.targetDate - ? new Date(improvement.targetDate).toISOString().split("T")[0] - : "", - status: improvement.status || "OPEN", - priority: improvement.priority || "MEDIUM", - ownerId: improvement.owner?.id || "", - }, - }, - ); - - const onSubmit = handleSubmit(async (formData) => { - try { - await updateImprovement({ - id: improvement.id!, - referenceId: formData.referenceId, - description: formData.description || undefined, - source: formData.source || undefined, - targetDate: formatDatetime(formData.targetDate) ?? null, - status: formData.status, - priority: formData.priority, - ownerId: formData.ownerId, - }); - - toast({ - title: __("Success"), - description: __("Continual improvement entry updated successfully"), - variant: "success", - }); - } catch (error) { - toast({ - title: __("Error"), - description: formatError( - __("Failed to update continual improvement"), - error as GraphQLError, - ), - variant: "error", - }); - } - }); - - const statusOptions = [ - { value: "OPEN", label: __("Open") }, - { value: "IN_PROGRESS", label: __("In Progress") }, - { value: "CLOSED", label: __("Closed") }, - ]; - - const priorityOptions = [ - { value: "LOW", label: __("Low") }, - { value: "MEDIUM", label: __("Medium") }, - { value: "HIGH", label: __("High") }, - ]; - - const breadcrumbImprovementsUrl = isSnapshotMode - ? `/organizations/${organizationId}/snapshots/${snapshotId}/continual-improvements` - : `/organizations/${organizationId}/continual-improvements`; - - return ( -
- {isSnapshotMode && snapshotId && ( - - )} -
- - {!isSnapshotMode && improvement.canDelete && ( - - - {__("Delete")} - - - )} -
- - -
-
-
-

{improvement.referenceId}

- - {getStatusLabel(improvement.status || "OPEN")} - - - {improvement.priority === "HIGH" - ? __("High") - : improvement.priority === "MEDIUM" - ? __("Medium") - : __("Low")} - -
-
- -
void onSubmit(e)} className="space-y-4"> - - -
- -