diff --git a/frontend/src/components/common/ConfirmDialog.tsx b/frontend/src/components/common/ConfirmDialog.tsx index a503fd43d9..9d2c0b0511 100644 --- a/frontend/src/components/common/ConfirmDialog.tsx +++ b/frontend/src/components/common/ConfirmDialog.tsx @@ -3,13 +3,13 @@ import MuiDialog, { DialogProps as MuiDialogProps } from '@mui/material/Dialog'; import DialogActions from '@mui/material/DialogActions'; import DialogContent from '@mui/material/DialogContent'; import DialogContentText from '@mui/material/DialogContentText'; -import React from 'react'; +import React, { ReactNode } from 'react'; import { useTranslation } from 'react-i18next'; import { DialogTitle } from './Dialog'; export interface ConfirmDialogProps extends MuiDialogProps { title: string; - description: string; + description: string | ReactNode; onConfirm: () => void; handleClose: () => void; } diff --git a/frontend/src/components/common/Resource/DeleteMultipleButton.tsx b/frontend/src/components/common/Resource/DeleteMultipleButton.tsx new file mode 100644 index 0000000000..c7b8119263 --- /dev/null +++ b/frontend/src/components/common/Resource/DeleteMultipleButton.tsx @@ -0,0 +1,109 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { useDispatch } from 'react-redux'; +import { KubeObject } from '../../../lib/k8s/KubeObject'; +import { CallbackActionOptions, clusterAction } from '../../../redux/clusterActionSlice'; +import { + EventStatus, + HeadlampEventType, + useEventCallback, +} from '../../../redux/headlampEventSlice'; +import { AppDispatch } from '../../../redux/stores/store'; +import ActionButton, { ButtonStyle } from '../ActionButton'; +import { ConfirmDialog } from '../Dialog'; + +interface DeleteMultipleButtonProps { + items?: KubeObject[]; + options?: CallbackActionOptions; + buttonStyle?: ButtonStyle; + afterConfirm?: () => void; +} + +interface DeleteMultipleButtonDescriptionProps { + items?: KubeObject[]; +} + +function DeleteMultipleButtonDescription(props: DeleteMultipleButtonDescriptionProps) { + const { t } = useTranslation(['translation']); + return ( +

+ {t('Are you sure you want to delete following items?')} +

+

+ ); +} + +export default function DeleteMultipleButton(props: DeleteMultipleButtonProps) { + const dispatch: AppDispatch = useDispatch(); + const { items, options, afterConfirm, buttonStyle } = props; + const [openAlert, setOpenAlert] = React.useState(false); + const { t } = useTranslation(['translation']); + const dispatchDeleteEvent = useEventCallback(HeadlampEventType.DELETE_RESOURCE); + + const deleteFunc = React.useCallback( + (item: KubeObject) => { + if (!item) { + return; + } + + const callback = item!.delete; + + const itemName = item!.metadata.name; + + callback && + dispatch( + clusterAction(callback.bind(item), { + startMessage: t('Deleting item {{ itemName }}…', { itemName }), + cancelledMessage: t('Cancelled deletion of {{ itemName }}.', { itemName }), + successMessage: t('Deleted item {{ itemName }}.', { itemName }), + errorMessage: t('Error deleting item {{ itemName }}.', { itemName }), + cancelUrl: location.pathname, + startUrl: item!.getListLink(), + errorUrl: item!.getListLink(), + ...options, + }) + ); + }, + // eslint-disable-next-line + [items] + ); + + if (!items || items.length === 0) { + return null; + } + + return ( + <> + { + setOpenAlert(true); + }} + icon="mdi:delete" + /> + } + handleClose={() => setOpenAlert(false)} + onConfirm={() => { + items.forEach(item => { + deleteFunc(item); + dispatchDeleteEvent({ + resource: item, + status: EventStatus.CONFIRMED, + }); + }); + if (afterConfirm) { + afterConfirm(); + } + }} + /> + + ); +} diff --git a/frontend/src/components/common/Resource/ResourceListView.tsx b/frontend/src/components/common/Resource/ResourceListView.tsx index 19e6334b23..23c0528dcd 100644 --- a/frontend/src/components/common/Resource/ResourceListView.tsx +++ b/frontend/src/components/common/Resource/ResourceListView.tsx @@ -45,7 +45,7 @@ export default function ResourceListView( ) } > - + {children} ); diff --git a/frontend/src/components/common/Resource/ResourceTable.tsx b/frontend/src/components/common/Resource/ResourceTable.tsx index 3ac3d4a01f..75b9558862 100644 --- a/frontend/src/components/common/Resource/ResourceTable.tsx +++ b/frontend/src/components/common/Resource/ResourceTable.tsx @@ -1,6 +1,6 @@ import { MenuItem, TableCellProps } from '@mui/material'; import { useTheme } from '@mui/material/styles'; -import { MRT_FilterFns, MRT_Row, MRT_SortingFn } from 'material-react-table'; +import { MRT_FilterFns, MRT_Row, MRT_SortingFn, MRT_TableInstance } from 'material-react-table'; import { ComponentProps, ReactNode, useEffect, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import helpers from '../../../helpers'; @@ -19,6 +19,7 @@ import Link from '../Link'; import Table, { TableColumn } from '../Table'; import DeleteButton from './DeleteButton'; import EditButton from './EditButton'; +import ResourceTableMultiActions from './ResourceTableMultiActions'; import { RestartButton } from './RestartButton'; import ScaleButton from './ScaleButton'; import ViewButton from './ViewButton'; @@ -83,6 +84,8 @@ export interface ResourceTableProps { columns: (ResourceTableColumn | ColumnType)[]; /** Show or hide row actions @default false*/ enableRowActions?: boolean; + /** Show or hide row selections and actions @default false*/ + enableRowSelection?: boolean; actions?: null | RowAction[]; /** Provide a list of columns that won't be shown and cannot be turned on */ hideColumns?: string[] | null; @@ -252,6 +255,7 @@ function ResourceTableContent(props: ResourceTablePr defaultGlobalFilter, actions, enableRowActions = false, + enableRowSelection = false, } = props; const { t } = useTranslation(['glossary', 'translation']); const theme = useTheme(); @@ -458,6 +462,15 @@ function ResourceTableContent(props: ResourceTablePr }; }, [actionsProcessed]); + const renderRowSelectionToolbar = useMemo(() => { + if (!enableRowSelection) { + return undefined; + } + return ({ table }: { table: MRT_TableInstance> }) => ( + + ); + }, [enableRowSelection]); + function onColumnsVisibilityChange(updater: any): void { setColumnVisibility(oldCols => { const newCols = updater(oldCols); @@ -490,6 +503,8 @@ function ResourceTableContent(props: ResourceTablePr >[]} diff --git a/frontend/src/components/common/Resource/ResourceTableMultiActions.tsx b/frontend/src/components/common/Resource/ResourceTableMultiActions.tsx new file mode 100644 index 0000000000..9258dc54bc --- /dev/null +++ b/frontend/src/components/common/Resource/ResourceTableMultiActions.tsx @@ -0,0 +1,21 @@ +import { MRT_TableInstance } from 'material-react-table'; +import { useCallback } from 'react'; +import { KubeObject } from '../../../lib/k8s/KubeObject'; +import DeleteMultipleButton from './DeleteMultipleButton'; + +export interface ResourceTableMultiActionsProps> { + table: MRT_TableInstance; +} + +export default function ResourceTableMultiActions>( + props: ResourceTableMultiActionsProps +) { + const { table } = props; + const items = table.getSelectedRowModel().rows.map(t => t.original as unknown as KubeObject); + + const afterConfirm = useCallback(() => { + table.resetRowSelection(); + }, [table]); + + return ; +} diff --git a/frontend/src/components/common/Resource/__snapshots__/ResourceListView.OneHiddenColumn.stories.storyshot b/frontend/src/components/common/Resource/__snapshots__/ResourceListView.OneHiddenColumn.stories.storyshot index 7eb42edaef..bc42c46b74 100644 --- a/frontend/src/components/common/Resource/__snapshots__/ResourceListView.OneHiddenColumn.stories.storyshot +++ b/frontend/src/components/common/Resource/__snapshots__/ResourceListView.OneHiddenColumn.stories.storyshot @@ -157,8 +157,8 @@ class="MuiTableContainer-root css-nhjqqh-MuiTableContainer-root" >
+ + + + + +
+
+ +
+
+
@@ -407,7 +456,7 @@ aria-sort="none" class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignLeft MuiTableCell-sizeMedium css-tb4te8-MuiTableCell-root" colspan="1" - data-index="3" + data-index="4" scope="col" >
+ + + + + +

+ + + + + +

+ + + + + +

+ + + + + +

+ + + + + +