diff --git a/packages/client/public/locales/en/translation.json b/packages/client/public/locales/en/translation.json index 6ccfbc20..b53aa97e 100644 --- a/packages/client/public/locales/en/translation.json +++ b/packages/client/public/locales/en/translation.json @@ -72,9 +72,11 @@ }, "datasetControl": { "deleteEntry": "Delete Entry", - "deleteDescription": "Are you sure you want to delete this project? Doing so will delete all associated tags", + "deleteEntries": "Delete Entries", + "deleteDescription": "Are you sure you want to delete this entry? Doing so will delete all cooresponding tags", "addDataset": " Add New Dataset", - "uploadEntries": " Upload Entries" + "uploadEntries": " Upload Entries", + "deleteMultipleEntries": "Are you sure you want to delete selected entries? All cooresponding tags will be deleted" }, "projectAccess": { "datasetName": "Dataset Name", diff --git a/packages/client/src/components/DatasetTable.component.tsx b/packages/client/src/components/DatasetTable.component.tsx index c21dbb2d..bea15293 100644 --- a/packages/client/src/components/DatasetTable.component.tsx +++ b/packages/client/src/components/DatasetTable.component.tsx @@ -1,25 +1,33 @@ -import { DataGrid, GridColDef } from '@mui/x-data-grid'; +import { DataGrid, GridColDef, GridRowId, GridActionsCellItem } from '@mui/x-data-grid'; import { useState, useEffect } from 'react'; import { Dataset, Entry } from '../graphql/graphql'; import { useEntryForDatasetLazyQuery } from '../graphql/entry/entry'; import { EntryView } from './EntryView.component'; import { useTranslation } from 'react-i18next'; import { useSnackbar } from '../context/Snackbar.context'; +import { Delete } from '@mui/icons-material'; +import { IconButton } from '@mui/material'; +import { useDeleteEntryMutation } from '../graphql/entry/entry'; +import { useConfirmation } from '../context/Confirmation.context'; export interface DatasetTableProps { dataset: Dataset; additionalColumns?: GridColDef[]; + supportEntryDelete?: boolean; } export const DatasetTable: React.FC = (props) => { const { t } = useTranslation(); const { pushSnackbarMessage } = useSnackbar(); + const [deleteEntryMutation] = useDeleteEntryMutation(); + const confirmation = useConfirmation(); + const [selectedRows, setSelectedRows] = useState([]); const defaultColumns: GridColDef[] = [ { field: 'view', headerName: t('common.view'), - width: 300, + width: 350, renderCell: (params) => }, { @@ -30,15 +38,80 @@ export const DatasetTable: React.FC = (props) => { } ]; + const handleMultiSelectDelete = () => { + confirmation.pushConfirmationRequest({ + title: t('components.datasetcontrol.deleteEntries'), + message: `${t('components.datasetControl.deleteMultipleEntries')}:${selectedRows.length}`, + onConfirm: async () => { + await Promise.all( + selectedRows.map((id) => { + return deleteEntryMutation({ variables: { entry: id.toString() } }); + }) + ); + reload(); + }, + onCancel: () => {} + }); + }; + + const handleDelete = async (id: GridRowId) => { + // Execute delete mutation + confirmation.pushConfirmationRequest({ + title: t('components.datasetControl.deleteEntry'), + message: t('components.datasetControl.deleteDescription'), + onConfirm: async () => { + const res = await deleteEntryMutation({ variables: { entry: id.toString() } }); + if (res.errors) { + //TODO show error with snackbar + } else if (res.data) { + reload(); + } + }, + onCancel: () => {} + }); + }; + + const deleteColumn: GridColDef = { + field: 'delete', + type: 'actions', + headerName: t('common.delete'), + width: 120, + maxWidth: 120, + cellClassName: 'delete', + renderHeader: () => { + return ( + handleMultiSelectDelete()}> + + + ); + }, + getActions: (params) => { + return [ + } + label={t('common.delete')} + onClick={() => handleDelete(params.id)} + /> + ]; + } + }; + const [entries, setEntries] = useState([]); const columns = [...defaultColumns, ...(props.additionalColumns ?? [])]; + if (props.supportEntryDelete) { + columns.push(deleteColumn); + } const [entryForDataset, entryForDatasetResult] = useEntryForDatasetLazyQuery(); useEffect(() => { - entryForDataset({ variables: { dataset: props.dataset._id }, fetchPolicy: 'network-only' }); + reload(); }, [props.dataset]); + const reload = () => { + entryForDataset({ variables: { dataset: props.dataset._id }, fetchPolicy: 'network-only' }); + }; + // TODO: Add in logic to re-fetch data when the presigned URL expires useEffect(() => { if (entryForDatasetResult.data) { @@ -63,6 +136,8 @@ export const DatasetTable: React.FC = (props) => { }} getRowId={(row) => row._id} pageSizeOptions={[5, 10, 15]} + onRowSelectionModelChange={(ids) => setSelectedRows(ids)} + rowSelectionModel={selectedRows} checkboxSelection disableRowSelectionOnClick /> diff --git a/packages/client/src/components/DatasetsView.component.tsx b/packages/client/src/components/DatasetsView.component.tsx index 6c38aab9..6ea022f5 100644 --- a/packages/client/src/components/DatasetsView.component.tsx +++ b/packages/client/src/components/DatasetsView.component.tsx @@ -7,10 +7,11 @@ import { GridColDef } from '@mui/x-data-grid'; export interface DatasetsViewProps { datasets: Dataset[]; additionalColumns?: GridColDef[]; + supportEntryDelete?: boolean; } // TODO: Implement lazy loading on accordion open to prevent loading all datasets at once -export const DatasetsView: React.FC = ({ datasets, additionalColumns }) => { +export const DatasetsView: React.FC = ({ datasets, additionalColumns, supportEntryDelete }) => { return ( <> {datasets.map((dataset: Dataset) => ( @@ -23,7 +24,11 @@ export const DatasetsView: React.FC = ({ datasets, additional {/* provide new dataset object to allow DatasetTable to refetch entries after entries are updated */} - + ))} diff --git a/packages/client/src/pages/datasets/DatasetControls.tsx b/packages/client/src/pages/datasets/DatasetControls.tsx index a66f93c5..a9d8310e 100644 --- a/packages/client/src/pages/datasets/DatasetControls.tsx +++ b/packages/client/src/pages/datasets/DatasetControls.tsx @@ -6,10 +6,6 @@ import { UploadEntries } from '../../components/UploadEntries.component'; import { Dataset } from '../../graphql/graphql'; import { useGetDatasetsLazyQuery } from '../../graphql/dataset/dataset'; import { DatasetsView } from '../../components/DatasetsView.component'; -import { GridColDef, GridActionsCellItem, GridRowId } from '@mui/x-data-grid'; -import DeleteIcon from '@mui/icons-material/DeleteOutlined'; -import { useConfirmation } from '../../context/Confirmation.context'; -import { useDeleteEntryMutation } from '../../graphql/entry/entry'; import { useTranslation } from 'react-i18next'; export const DatasetControls: React.FC = () => { @@ -17,11 +13,8 @@ export const DatasetControls: React.FC = () => { const [upload, setUpload] = useState(false); const [datasets, setDatasets] = useState([]); const [getDatasets, getDatasetsResults] = useGetDatasetsLazyQuery(); - const [deleteEntryMutation] = useDeleteEntryMutation(); const { t } = useTranslation(); - const confirmation = useConfirmation(); - useEffect(() => { getDatasets(); }, []); @@ -51,44 +44,6 @@ export const DatasetControls: React.FC = () => { setUpload((upload) => !upload); }; - const handleDelete = async (id: GridRowId) => { - // Execute delete mutation - confirmation.pushConfirmationRequest({ - title: t('components.datasetControl.deleteEntry'), - message: t('components.datasetControl.deleteDescription'), - onConfirm: async () => { - const res = await deleteEntryMutation({ variables: { entry: id.toString() } }); - if (res.errors) { - //TODO show error with snackbar - } else if (res.data) { - // force rerender - setDatasets([...datasets]); - } - }, - onCancel: () => {} - }); - }; - - const additionalColumns: GridColDef[] = [ - { - field: 'delete', - type: 'actions', - headerName: t('common.delete'), - width: 120, - maxWidth: 120, - cellClassName: 'delete', - getActions: (params) => { - return [ - } - label={t('common.delete')} - onClick={() => handleDelete(params.id)} - /> - ]; - } - } - ]; - return ( <> {t('menu.datasetControl')} @@ -116,7 +71,7 @@ export const DatasetControls: React.FC = () => { - + ); };