Replace console frontend with unified findings pages

Add FindingsPage, FindingDetailsPage, and CreateFindingDialog
supporting all finding kinds (nonconformity, observation, exception)
with filtering, sorting, and audit linking.

Remove the separate nonconformity and continual improvement pages,
routes, and graph hooks. Update sidebar navigation, routes, and
components for nullable audit framework field.

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
Bryan Frimin
2026-03-17 00:38:44 +01:00
parent ad0cf38e76
commit 0e0539f1f3
22 changed files with 1530 additions and 2500 deletions

View File

@@ -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 ?? "",
),
},
);

View File

@@ -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,
},
});
};
};

View File

@@ -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,
},
},
});
};
};