diff --git a/src/components/sectionList/SectionListWrapper.tsx b/src/components/sectionList/SectionListWrapper.tsx index 03048d63..ea2122cf 100644 --- a/src/components/sectionList/SectionListWrapper.tsx +++ b/src/components/sectionList/SectionListWrapper.tsx @@ -1,12 +1,7 @@ import { FetchError } from '@dhis2/app-runtime' import { SharingDialog } from '@dhis2/ui' import React, { useCallback, useState } from 'react' -import { - BaseListModel, - canEditModel, - useSchemaFromHandle, - useDeleteModelMutation, -} from '../../lib' +import { BaseListModel, canEditModel, useSchemaFromHandle } from '../../lib' import { Pager, ModelCollection } from '../../types/models' import { SectionListHeaderBulk } from './bulk' import { DetailsPanel, DefaultDetailsPanelContent } from './detailsPanel' @@ -44,14 +39,6 @@ export const SectionListWrapper = ({ useSelectedModels() const [detailsId, setDetailsId] = useState() const [sharingDialogId, setSharingDialogId] = useState() - const deleteModelMutation = useDeleteModelMutation(schema) - const deleteModel = useCallback( - async ({ id, displayName }: BaseListModel) => { - await deleteModelMutation.mutateAsync({ id, displayName }) - refetch() - }, - [deleteModelMutation, refetch] - ) const SectionListMessage = () => { if (error) { @@ -113,15 +100,10 @@ export const SectionListWrapper = ({ modelType={schema.displayName} onShowDetailsClick={handleDetailsClick} onOpenSharingClick={setSharingDialogId} - onDeleteClick={() => deleteModel(model)} + onDeleteSuccess={refetch} /> ), - [ - handleDetailsClick, - setSharingDialogId, - deleteModel, - schema.displayName, - ] + [handleDetailsClick, setSharingDialogId, schema.displayName, refetch] ) const isAllSelected = data ? checkAllSelected(data) : false diff --git a/src/components/sectionList/listActions/DefaultListActions.tsx b/src/components/sectionList/listActions/DefaultListActions.tsx index 9c633910..f889282e 100644 --- a/src/components/sectionList/listActions/DefaultListActions.tsx +++ b/src/components/sectionList/listActions/DefaultListActions.tsx @@ -8,7 +8,7 @@ type DefaultListActionProps = { modelType: string onShowDetailsClick: (model: BaseListModel) => void onOpenSharingClick: (id: string) => void - onDeleteClick: () => void + onDeleteSuccess: () => void } export const DefaultListActions = ({ @@ -16,7 +16,7 @@ export const DefaultListActions = ({ modelType, onShowDetailsClick, onOpenSharingClick, - onDeleteClick, + onDeleteSuccess, }: DefaultListActionProps) => { const deletable = canDeleteModel(model) const editable = canEditModel(model) @@ -25,13 +25,14 @@ export const DefaultListActions = ({ onShowDetailsClick(model)} onOpenSharingClick={() => onOpenSharingClick(model.id)} - onDeleteClick={onDeleteClick} - modelType={modelType} + onDeleteSuccess={onDeleteSuccess} /> ) diff --git a/src/components/sectionList/listActions/DeleteAction.module.css b/src/components/sectionList/listActions/DeleteAction.module.css new file mode 100644 index 00000000..c61f208a --- /dev/null +++ b/src/components/sectionList/listActions/DeleteAction.module.css @@ -0,0 +1,8 @@ +.deleteButtonLoadingIcon { + display: inline-block; + margin-right: 8px; +} + +.deleteButtonLoadingIcon :global([role="progressbar"]) { + border-color: rgba(110, 122, 138, 0.15) rgba(110, 122, 138, 0.15) white; +} diff --git a/src/components/sectionList/listActions/DeleteAction.tsx b/src/components/sectionList/listActions/DeleteAction.tsx index 476a08e0..74895028 100644 --- a/src/components/sectionList/listActions/DeleteAction.tsx +++ b/src/components/sectionList/listActions/DeleteAction.tsx @@ -2,55 +2,61 @@ import i18n from '@dhis2/d2-i18n' import { Button, ButtonStrip, + CircularLoader, IconDelete16, MenuItem, Modal, ModalActions, + ModalContent, ModalTitle, + NoticeBox, } from '@dhis2/ui' import React, { useState } from 'react' -import { TOOLTIPS } from '../../../lib' -import { TooltipWrapper } from '../../tooltip' +import { useSchemaFromHandle, useDeleteModelMutation } from '../../../lib' +import classes from './DeleteAction.module.css' export function DeleteAction({ - deletable, + disabled, + modelId, + modelDisplayName, modelType, - onClick, onCancel, + onDeleteSuccess, }: { - deletable: boolean + disabled: boolean + modelId: string + modelDisplayName: string modelType: string - onClick: () => void onCancel: () => void + onDeleteSuccess: () => void }) { const [showConfirmationDialog, setShowConfirmationDialog] = useState(false) + const deleteAndClose = () => { + setShowConfirmationDialog(false) + onDeleteSuccess() + } + const closeAndCancel = () => { + setShowConfirmationDialog(false) + onCancel() + } return ( <> - - } - onClick={() => setShowConfirmationDialog(true)} - /> - + } + onClick={() => setShowConfirmationDialog(true)} + /> {showConfirmationDialog && ( { - setShowConfirmationDialog(false) - onClick() - }} - onCancel={() => { - setShowConfirmationDialog(false) - onCancel() - }} + onDeleteSuccess={deleteAndClose} + onCancel={closeAndCancel} /> )} @@ -58,31 +64,97 @@ export function DeleteAction({ } function ConfirmationDialog({ + modelId, + modelDisplayName, modelType, onCancel, - onConfirm, + onDeleteSuccess, }: { + modelId: string + modelDisplayName: string modelType: string onCancel: () => void - onConfirm: () => void + onDeleteSuccess: () => void }) { + const schema = useSchemaFromHandle() + const deleteModelMutation = useDeleteModelMutation(schema) + const deleteModel = async () => { + await deleteModelMutation.mutateAsync({ + id: modelId, + displayName: modelDisplayName, + }) + onDeleteSuccess() + } + + const deleteAndClose = () => + deleteModel() + .then(onDeleteSuccess) + // We don't need to do anything on error except for catching it, + // we have all the information on the deleteModelMutation value + .catch(() => null) + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const errorReports = (deleteModelMutation.error as any)?.details?.response + ?.errorReports + return ( {i18n.t( 'Are you sure that you want to delete this {{modelType}}?', - { - modelType: modelType, - } + { modelType } )} + {!!deleteModelMutation.error && ( + + +
+ {i18n.t( + 'Failed to delete {{modelType}} "{{modelDisplayName}}"! {{messages}}', + { modelDisplayName, modelType } + )} +
+ + {!!errorReports?.length && ( +
    + {errorReports.map( + // @TODO: I have no idea why TS says "message" isn't being used? + // eslint-disable-next-line react/no-unused-prop-types + ({ message }: { message: string }) => ( +
  • {message}
  • + ) + )} +
+ )} +
+
+ )} + - - +
diff --git a/src/components/sectionList/listActions/SectionListActions.tsx b/src/components/sectionList/listActions/SectionListActions.tsx index 3aae42db..7a0dcea7 100644 --- a/src/components/sectionList/listActions/SectionListActions.tsx +++ b/src/components/sectionList/listActions/SectionListActions.tsx @@ -31,22 +31,24 @@ export const ActionEdit = ({ modelId }: { modelId: string }) => { } type ActionMoreProps = { - modelId: string deletable: boolean editable: boolean + modelId: string + modelDisplayName: string modelType: string onShowDetailsClick: () => void onOpenSharingClick: () => void - onDeleteClick: () => void + onDeleteSuccess: () => void } export const ActionMore = ({ - modelId, deletable, editable, + modelId, + modelDisplayName, modelType, onOpenSharingClick, onShowDetailsClick, - onDeleteClick, + onDeleteSuccess, }: ActionMoreProps) => { const [open, setOpen] = useState(false) const ref = useRef(null) @@ -108,15 +110,22 @@ export const ActionMore = ({ /> - setOpen(false)} - onClick={() => { - onDeleteClick() - setOpen(false) - }} - /> + + { + onDeleteSuccess() + setOpen(false) + }} + onCancel={() => setOpen(false)} + /> + )}