Add delete evidence

Signed-off-by: gearnode <bryan@frimin.fr>
This commit is contained in:
gearnode
2025-03-01 21:02:28 +01:00
parent 3fb04c99fe
commit 10b9ded41c
11 changed files with 584 additions and 4 deletions

View File

@@ -53,6 +53,7 @@ import type { ControlOverviewPageUpdateTaskStateMutation as ControlOverviewPageU
import type { ControlOverviewPageCreateTaskMutation as ControlOverviewPageCreateTaskMutationType } from "./__generated__/ControlOverviewPageCreateTaskMutation.graphql";
import type { ControlOverviewPageDeleteTaskMutation as ControlOverviewPageDeleteTaskMutationType } from "./__generated__/ControlOverviewPageDeleteTaskMutation.graphql";
import type { ControlOverviewPageUploadEvidenceMutation as ControlOverviewPageUploadEvidenceMutationType } from "./__generated__/ControlOverviewPageUploadEvidenceMutation.graphql";
import type { ControlOverviewPageDeleteEvidenceMutation as ControlOverviewPageDeleteEvidenceMutationType } from "./__generated__/ControlOverviewPageDeleteEvidenceMutation.graphql";
const controlOverviewPageQuery = graphql`
query ControlOverviewPageQuery($controlId: ID!) {
@@ -155,6 +156,17 @@ const uploadEvidenceMutation = graphql`
}
`;
const deleteEvidenceMutation = graphql`
mutation ControlOverviewPageDeleteEvidenceMutation(
$input: DeleteEvidenceInput!
$connections: [ID!]!
) {
deleteEvidence(input: $input) {
deletedEvidenceId @deleteEdge(connections: $connections)
}
}
`;
// Add a GraphQL query to fetch the fileUrl for an evidence item
const getEvidenceFileUrlQuery = graphql`
query ControlOverviewPageGetEvidenceFileUrlQuery($evidenceId: ID!) {
@@ -192,6 +204,10 @@ function ControlOverviewPageContent({
useMutation<ControlOverviewPageUploadEvidenceMutationType>(
uploadEvidenceMutation
);
const [deleteEvidence] =
useMutation<ControlOverviewPageDeleteEvidenceMutationType>(
deleteEvidenceMutation
);
const [isCreateTaskOpen, setIsCreateTaskOpen] = useState(false);
const [newTaskName, setNewTaskName] = useState("");
@@ -232,6 +248,13 @@ function ControlOverviewPageContent({
} | null>(null);
const [isLoadingFileUrl, setIsLoadingFileUrl] = useState(false);
const [isDeleteEvidenceOpen, setIsDeleteEvidenceOpen] = useState(false);
const [evidenceToDelete, setEvidenceToDelete] = useState<{
id: string;
filename: string;
taskId: string;
} | null>(null);
const tasks = data.control.tasks?.edges.map((edge) => edge.node) || [];
const getEvidenceConnectionId = useCallback(
@@ -604,6 +627,47 @@ function ControlOverviewPageContent({
});
};
const handleDeleteEvidence = (
evidenceId: string,
filename: string,
taskId: string
) => {
setEvidenceToDelete({ id: evidenceId, filename, taskId });
setIsDeleteEvidenceOpen(true);
};
const confirmDeleteEvidence = () => {
if (!evidenceToDelete) return;
const evidenceConnectionId = getEvidenceConnectionId(
evidenceToDelete.taskId
);
deleteEvidence({
variables: {
input: {
evidenceId: evidenceToDelete.id,
},
connections: evidenceConnectionId ? [evidenceConnectionId] : [],
},
onCompleted: () => {
toast({
title: "Evidence deleted",
description: "Evidence has been deleted successfully.",
});
setIsDeleteEvidenceOpen(false);
setEvidenceToDelete(null);
},
onError: (error) => {
toast({
title: "Error deleting evidence",
description: error.message,
variant: "destructive",
});
},
});
};
return (
<>
<Helmet>
@@ -928,6 +992,20 @@ function ControlOverviewPageContent({
>
<Download className="w-4 h-4 text-gray-600" />
</button>
<button
onClick={(e) => {
e.preventDefault();
handleDeleteEvidence(
evidence.id,
evidence.filename,
task.id
);
}}
className="p-1 rounded-full hover:bg-gray-100 hover:bg-red-50 hover:text-red-600"
title="Delete Evidence"
>
<Trash2 className="w-4 h-4 text-gray-600 hover:text-red-600" />
</button>
</div>
</div>
);
@@ -1103,6 +1181,34 @@ function ControlOverviewPageContent({
</DialogFooter>
</DialogContent>
</Dialog>
{/* Add Delete Evidence Confirmation Dialog */}
<Dialog
open={isDeleteEvidenceOpen}
onOpenChange={setIsDeleteEvidenceOpen}
>
<DialogContent>
<DialogHeader>
<DialogTitle>Delete Evidence</DialogTitle>
<DialogDescription>
Are you sure you want to delete the evidence &quot;
{evidenceToDelete?.filename}&quot;? This action cannot be
undone.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button
variant="outline"
onClick={() => setIsDeleteEvidenceOpen(false)}
>
Cancel
</Button>
<Button variant="destructive" onClick={confirmDeleteEvidence}>
Delete
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
</>
);

View File

@@ -0,0 +1,132 @@
/**
* @generated SignedSource<<c09b83c20157fc20bf74a2c9c031b716>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type DeleteEvidenceInput = {
evidenceId: string;
};
export type ControlOverviewPageDeleteEvidenceMutation$variables = {
connections: ReadonlyArray<string>;
input: DeleteEvidenceInput;
};
export type ControlOverviewPageDeleteEvidenceMutation$data = {
readonly deleteEvidence: {
readonly deletedEvidenceId: string;
};
};
export type ControlOverviewPageDeleteEvidenceMutation = {
response: ControlOverviewPageDeleteEvidenceMutation$data;
variables: ControlOverviewPageDeleteEvidenceMutation$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": "deletedEvidenceId",
"storageKey": null
};
return {
"fragment": {
"argumentDefinitions": [
(v0/*: any*/),
(v1/*: any*/)
],
"kind": "Fragment",
"metadata": null,
"name": "ControlOverviewPageDeleteEvidenceMutation",
"selections": [
{
"alias": null,
"args": (v2/*: any*/),
"concreteType": "DeleteEvidencePayload",
"kind": "LinkedField",
"name": "deleteEvidence",
"plural": false,
"selections": [
(v3/*: any*/)
],
"storageKey": null
}
],
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": [
(v1/*: any*/),
(v0/*: any*/)
],
"kind": "Operation",
"name": "ControlOverviewPageDeleteEvidenceMutation",
"selections": [
{
"alias": null,
"args": (v2/*: any*/),
"concreteType": "DeleteEvidencePayload",
"kind": "LinkedField",
"name": "deleteEvidence",
"plural": false,
"selections": [
(v3/*: any*/),
{
"alias": null,
"args": null,
"filters": null,
"handle": "deleteEdge",
"key": "",
"kind": "ScalarHandle",
"name": "deletedEvidenceId",
"handleArgs": [
{
"kind": "Variable",
"name": "connections",
"variableName": "connections"
}
]
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "51d62fb682189fa0aaa5cc5b7a5b7862",
"id": null,
"metadata": {},
"name": "ControlOverviewPageDeleteEvidenceMutation",
"operationKind": "mutation",
"text": "mutation ControlOverviewPageDeleteEvidenceMutation(\n $input: DeleteEvidenceInput!\n) {\n deleteEvidence(input: $input) {\n deletedEvidenceId\n }\n}\n"
}
};
})();
(node as any).hash = "f55cf1c279c95dcabc11e1b5aafcdc46";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<3c761f476fc4dcbc2f175eefa625bb28>>
* @generated SignedSource<<e1f21afb2bf00563c8343f3700c08c6e>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -31,7 +31,7 @@ export type ControlOverviewPageQuery$data = {
readonly __id: string;
readonly edges: ReadonlyArray<{
readonly node: {
readonly createdAt: any;
readonly createdAt: string;
readonly filename: string;
readonly id: string;
readonly mimeType: string;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<09af19b5a7657e04606a42962db9db2c>>
* @generated SignedSource<<f1cdd20d14ca9fefe868019e5aa43741>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -23,7 +23,7 @@ export type ControlOverviewPageUploadEvidenceMutation$data = {
readonly uploadEvidence: {
readonly evidenceEdge: {
readonly node: {
readonly createdAt: any;
readonly createdAt: string;
readonly fileUrl: string;
readonly id: string;
readonly mimeType: string;