diff --git a/submit-api/src/submit_api/resources/staff/internal_document.py b/submit-api/src/submit_api/resources/staff/internal_document.py index 4aa3fb78..05f4235f 100644 --- a/submit-api/src/submit_api/resources/staff/internal_document.py +++ b/submit-api/src/submit_api/resources/staff/internal_document.py @@ -37,8 +37,8 @@ ) -@cors_preflight("OPTIONS, POST") -@API.route("/submission-items/", methods=["POST", "OPTIONS"]) +@cors_preflight("OPTIONS, POST, DELETE") +@API.route("/submission-items/", methods=["POST", "OPTIONS", "DELETE"]) class InternalStaffDocuments(Resource): """Resource for managing projects.""" @@ -57,3 +57,37 @@ def post(submission_item_id): created_document = (InternalStaffDocumentService .create_internal_staff_document(submission_item_id, create_document_data)) return InternalStaffDocument().dump(created_document), HTTPStatus.CREATED + + @staticmethod + @ApiHelper.swagger_decorators(API, endpoint_description="Delete an internal staff document") + @API.response( + code=HTTPStatus.OK, model=internal_document, description="Deleted Internal Staff Document" + ) + @API.response(HTTPStatus.NOT_FOUND, "Not found") + @auth.has_one_of_roles([EpicSubmitRole.EAO_CREATE.value]) + @cors.crossdomain(origin="*") + def delete(internal_staff_document_id): + """Delete an internal staff document.""" + deleted_document = (InternalStaffDocumentService + .delete_internal_staff_document(internal_staff_document_id)) + return InternalStaffDocument().dump(deleted_document), HTTPStatus.OK + + +@cors_preflight("OPTIONS, DELETE") +@API.route("/", methods=["OPTIONS", "DELETE"]) +class InternalStaffDocuments(Resource): + """Resource for managing projects.""" + + @staticmethod + @ApiHelper.swagger_decorators(API, endpoint_description="Delete an internal staff document") + @API.response( + code=HTTPStatus.OK, model=internal_document, description="Deleted Internal Staff Document" + ) + @API.response(HTTPStatus.NOT_FOUND, "Not found") + @auth.has_one_of_roles([EpicSubmitRole.EAO_CREATE.value]) + @cors.crossdomain(origin="*") + def delete(internal_staff_document_id): + """Delete an internal staff document.""" + deleted_document = (InternalStaffDocumentService + .delete_internal_staff_document(internal_staff_document_id)) + return InternalStaffDocument().dump(deleted_document), HTTPStatus.OK diff --git a/submit-api/src/submit_api/services/internal_staff_document_service.py b/submit-api/src/submit_api/services/internal_staff_document_service.py index b603ebc5..bda5b893 100644 --- a/submit-api/src/submit_api/services/internal_staff_document_service.py +++ b/submit-api/src/submit_api/services/internal_staff_document_service.py @@ -39,3 +39,12 @@ def create_internal_staff_document(cls, submission_item_id, data): ) internal_staff_document.save() return internal_staff_document + + @classmethod + def delete_internal_staff_document(cls, internal_staff_document_id): + """Delete internal staff document.""" + internal_staff_document = InternalStaffDocumentModel.find_by_id(internal_staff_document_id) + if not internal_staff_document: + raise ResourceNotFoundError("Internal staff document not found") + internal_staff_document.delete() + return internal_staff_document diff --git a/submit-web/src/components/DocumentUpload/DocumentTableRow.tsx b/submit-web/src/components/DocumentUpload/DocumentTableRow.tsx index 83d55414..2de362e8 100644 --- a/submit-web/src/components/DocumentUpload/DocumentTableRow.tsx +++ b/submit-web/src/components/DocumentUpload/DocumentTableRow.tsx @@ -12,7 +12,7 @@ import { deleteDocument, downloadObject } from "@/hooks/api/useObjectStorage"; import { notify } from "../Shared/Snackbar/snackbarStore"; import { Submission } from "@/models/Submission"; import { LoadingButton } from "../Shared/LoadingButton"; -import { deleteSubmission } from "@/hooks/api/useSubmissions"; +import { useDeleteSubmission } from "@/hooks/api/useSubmissions"; export const StyledHeadTableCell = styled(TableCell)<{ error?: boolean }>( ({ error }) => ({ @@ -99,6 +99,10 @@ export default function DocumentTableRow({ const [pendingGetObject, setPendingGetObject] = useState(false); const [isRemovingDocument, setIsRemovingDocument] = useState(false); + const { mutateAsync: deleteSubmission } = useDeleteSubmission({ + submissionItemId: documentItem.item_id, + }); + const getObjectFromS3 = async () => { try { if (pendingGetObject) return; diff --git a/submit-web/src/components/Submission/ItemsTable.tsx b/submit-web/src/components/Submission/ItemsTable.tsx index ce7f48ac..f439659a 100644 --- a/submit-web/src/components/Submission/ItemsTable.tsx +++ b/submit-web/src/components/Submission/ItemsTable.tsx @@ -80,6 +80,7 @@ export default function ItemsTable({ submissionPackage }: ItemsTableProps) { diff --git a/submit-web/src/components/SubmissionItem/InternalDocuments/Row.tsx b/submit-web/src/components/SubmissionItem/InternalDocuments/Row.tsx index d512c60a..6da39a50 100644 --- a/submit-web/src/components/SubmissionItem/InternalDocuments/Row.tsx +++ b/submit-web/src/components/SubmissionItem/InternalDocuments/Row.tsx @@ -4,14 +4,38 @@ import { InternalStaffDocument } from "@/models/SubmissionItem"; import { getObjectFromS3 } from "@/components/Shared/Table/utils"; import { notify } from "@/components/Shared/Snackbar/snackbarStore"; import { SubmitTableCell } from "@/components/Shared/Table/common"; +import { LoadingButton } from "@/components/Shared/LoadingButton"; +import { BCDesignTokens } from "epic.theme"; +import { useDeleteInternalStaffDocument } from "@/hooks/api/useInternalStaffDocuments"; +import { useParams } from "@tanstack/react-router"; +import { deleteDocument } from "@/hooks/api/useObjectStorage"; +import { Unless } from "react-if"; type RowProps = { internalStaffDocument: InternalStaffDocument; numColumns: number; + setDocuments: React.Dispatch>; + hideAction?: boolean; }; -export default function Row({ internalStaffDocument, numColumns }: RowProps) { +export default function Row({ + internalStaffDocument, + numColumns, + setDocuments, + hideAction = false, +}: RowProps) { + const { submissionPackageId, submissionId: submissionItemId } = useParams({ + strict: false, + }); const [pendingGetObject, setPendingGetObject] = useState(false); + const [isRemovingDocument, setIsRemovingDocument] = useState(false); + + const { mutateAsync: deleteInternalStaffSubmission } = + useDeleteInternalStaffDocument({ + packageId: Number(submissionPackageId), + itemId: Number(submissionItemId), + }); + const { name, url } = internalStaffDocument; const downloadDocument = async () => { @@ -25,6 +49,23 @@ export default function Row({ internalStaffDocument, numColumns }: RowProps) { setPendingGetObject(false); } }; + + const onRemoveClick = async () => { + try { + setIsRemovingDocument(true); + await deleteDocument({ filepath: internalStaffDocument.url }); + await deleteInternalStaffSubmission({ + documentId: internalStaffDocument.id, + }); + setDocuments((prev) => + prev.filter((sub) => sub.id !== internalStaffDocument.id), + ); + } catch (e) { + notify.error("Failed to remove document"); + } finally { + setIsRemovingDocument(false); + } + }; return ( @@ -41,7 +82,27 @@ export default function Row({ internalStaffDocument, numColumns }: RowProps) { {name} - + + + + + Remove + + + ); } diff --git a/submit-web/src/components/SubmissionItem/InternalDocuments/Rows.tsx b/submit-web/src/components/SubmissionItem/InternalDocuments/Rows.tsx index 109874df..7d7966ce 100644 --- a/submit-web/src/components/SubmissionItem/InternalDocuments/Rows.tsx +++ b/submit-web/src/components/SubmissionItem/InternalDocuments/Rows.tsx @@ -8,20 +8,24 @@ import { SubmitPrimaryRowTableCell, SubmitTablePrimaryRow, } from "@/components/Shared/Table/common"; +import { useState } from "react"; type InternalDocumentsProps = Readonly<{ internalStaffDocuments: Array; numColumns?: number; + hideAction?: boolean; }>; export default function Rows({ internalStaffDocuments, numColumns = 4, + hideAction = false, }: InternalDocumentsProps) { + const [documents, setDocuments] = useState>( + internalStaffDocuments, + ); const { uploadObjects: pendingDocuments } = useObjectUploadStore(); - const internalStaffDocumentsIds = new Set( - internalStaffDocuments.map((doc) => doc.id), - ); + const internalStaffDocumentsIds = new Set(documents.map((doc) => doc.id)); const filteredPendingDocuments = pendingDocuments.filter( (doc) => !internalStaffDocumentsIds.has(doc.submissionId ?? 0), @@ -56,6 +60,8 @@ export default function Rows({ key={`doc-row-${document.id}`} internalStaffDocument={document} numColumns={5} + setDocuments={setDocuments} + hideAction={hideAction} /> ))} {filteredPendingDocuments.map((pendingDocument) => ( diff --git a/submit-web/src/components/SubmissionItem/InternalDocuments/Table.tsx b/submit-web/src/components/SubmissionItem/InternalDocuments/Table.tsx index d131e6fa..a33f2fd2 100644 --- a/submit-web/src/components/SubmissionItem/InternalDocuments/Table.tsx +++ b/submit-web/src/components/SubmissionItem/InternalDocuments/Table.tsx @@ -49,7 +49,7 @@ export default function Table({ diff --git a/submit-web/src/hooks/api/useInternalStaffDocuments.ts b/submit-web/src/hooks/api/useInternalStaffDocuments.ts index f9f3c971..cbd3ab8e 100644 --- a/submit-web/src/hooks/api/useInternalStaffDocuments.ts +++ b/submit-web/src/hooks/api/useInternalStaffDocuments.ts @@ -46,3 +46,43 @@ export const useCreateInternalStaffDocument = ({ }, }); }; + +type DeleteInternalStaffDocumentProps = { + documentId: number; +}; +export const deleteInternalStaffDocument = ({ + documentId, +}: DeleteInternalStaffDocumentProps) => { + return submitRequest({ + url: `/staff/internal-staff-documents/${documentId}`, + method: "delete", + }); +}; + +type UseDeleteInternalStaffDocumentProps = { + itemId: number; + packageId: number; + options?: Options; +}; +export const useDeleteInternalStaffDocument = ({ + itemId, + packageId, + options, +}: UseDeleteInternalStaffDocumentProps) => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: deleteInternalStaffDocument, + ...options, + onSuccess: () => { + if (options?.onSuccess) { + options.onSuccess(); + } + queryClient.invalidateQueries({ + queryKey: [QUERY_KEY.SUBMISSION_ITEM, itemId], + }); + queryClient.invalidateQueries({ + queryKey: [QUERY_KEY.SUBMISSION_PACKAGE, packageId], + }); + }, + }); +}; diff --git a/submit-web/src/hooks/api/useSubmissions.ts b/submit-web/src/hooks/api/useSubmissions.ts index 2360f549..fc4295a6 100644 --- a/submit-web/src/hooks/api/useSubmissions.ts +++ b/submit-web/src/hooks/api/useSubmissions.ts @@ -162,3 +162,26 @@ export const deleteSubmission = (submissionId: number) => { method: "delete", }); }; + +type UseDeleteSubmissionParams = { + submissionItemId: number; +} & Options; +export const useDeleteSubmission = ({ + submissionItemId, + ...options +}: UseDeleteSubmissionParams) => { + const { onSuccess: _onSuccess, ...restOptions } = options; + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: deleteSubmission, + onSuccess: (data) => { + if (_onSuccess) { + _onSuccess(data); + } + queryClient.invalidateQueries({ + queryKey: [QUERY_KEY.SUBMISSION_ITEM, submissionItemId], + }); + }, + ...restOptions, + }); +};