/* * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ import { CaseAction } from '@aws/dea-app/lib/models/case-action'; import { CaseFileStatus } from '@aws/dea-app/lib/models/case-file-status'; import { CaseStatus } from '@aws/dea-app/lib/models/case-status'; import { useCollection } from '@cloudscape-design/collection-hooks'; import { Button, Link, Pagination, PropertyFilter, SpaceBetween, StatusIndicator, Table, Toggle, } from '@cloudscape-design/components'; import Box from '@cloudscape-design/components/box'; import Modal from '@cloudscape-design/components/modal'; import { useRouter } from 'next/router'; import * as React from 'react'; import { useAvailableEndpoints } from '../../api/auth'; import { DeaListResult, updateCaseStatus } from '../../api/cases'; import { DeaCaseDTO } from '../../api/models/case'; import { caseListLabels, caseStatusLabels, commonLabels, commonTableLabels, paginationLabels, } from '../../common/labels'; import { useNotifications } from '../../context/NotificationsContext'; import { formatDateFromISOString } from '../../helpers/dateHelper'; import { formatFileSize } from '../../helpers/fileHelper'; import { canCreateCases, canDeleteCaseFiles, canUpdateCaseStatus } from '../../helpers/userActionSupport'; import { TableEmptyDisplay, TableNoMatchDisplay } from '../common-components/CommonComponents'; import { i18nStrings } from '../common-components/commonDefinitions'; import { ConfirmModal } from '../common-components/ConfirmModal'; import { TableHeader } from '../common-components/TableHeader'; import { filteringOptions, filteringProperties, searchableColumns } from './caseListDefinitions'; export type CaseFetcherSignature = () => DeaListResult; export interface CaseTableProps { useCaseFetcher: CaseFetcherSignature; canCreate: boolean; detailPage: string; headerLabel: string; headerDescription: string; } function CaseTable(props: CaseTableProps): JSX.Element { const router = useRouter(); const availableEndpoints = useAvailableEndpoints(); const { data, isLoading, mutate } = props.useCaseFetcher(); const [selectedCase, setSelectedCase] = React.useState([]); const [showActivateModal, setShowActivateModal] = React.useState(false); const [showDeactivateModal, setShowDeactivateModal] = React.useState(false); const [deleteFiles, setDeleteFiles] = React.useState(false); const { pushNotification } = useNotifications(); // Property and date filter collections const { items, filteredItemsCount, propertyFilterProps, collectionProps, paginationProps } = useCollection( data, { filtering: { empty: TableEmptyDisplay(caseListLabels.noCasesLabel, caseListLabels.noDisplayLabel), noMatch: TableNoMatchDisplay(caseListLabels.noCasesMatchLabel), // eslint-disable-next-line @typescript-eslint/no-explicit-any filteringFunction: (item: any, filteringText): any => { const filteringTextLowerCase = filteringText.toLowerCase(); return ( searchableColumns // eslint-disable-next-line security/detect-object-injection .map((key) => item[key]) .some( (value) => typeof value === 'string' && value.toLowerCase().indexOf(filteringTextLowerCase) > -1 ) ); }, }, propertyFiltering: { filteringProperties: filteringProperties, empty: TableEmptyDisplay(caseListLabels.noCasesLabel, caseListLabels.noDisplayLabel), noMatch: TableNoMatchDisplay(caseListLabels.noCasesMatchLabel), }, sorting: {}, selection: {}, pagination: { pageSize: 15, }, } ); function createNewCaseHandler() { return router.push('/create-cases'); } function canActivateCase(): boolean { return selectedCase.length === 1 && selectedCase[0].status === CaseStatus.INACTIVE; } async function activateCaseHandler() { if (selectedCase.length === 0) { console.error('No cases selected for activate'); } const deaCase = selectedCase[0]; try { await updateCaseStatus({ name: deaCase.name, caseId: deaCase.ulid, status: CaseStatus.ACTIVE, deleteFiles: false, }); disableActivateCaseModal(); pushNotification('success', `${deaCase.name} has been activated.`); mutate(); } catch (e) { pushNotification('error', `Failed to activate ${deaCase.name}.`); } setSelectedCase([]); } function canDeactivateCase(): boolean { return ( selectedCase.length === 1 && (selectedCase[0].status === CaseStatus.ACTIVE || [CaseFileStatus.DELETE_FAILED, CaseFileStatus.ACTIVE].includes(selectedCase[0].filesStatus)) ); } async function deactivateCaseHandler() { if (selectedCase.length === 0) { console.error('No cases selected for deactivate'); } const deaCase = selectedCase[0]; try { const updatePromise = updateCaseStatus({ name: deaCase.name, caseId: deaCase.ulid, status: CaseStatus.INACTIVE, deleteFiles, }); disableDeactivateCaseModal(); await updatePromise; pushNotification('success', `${deaCase.name} has been deactivated.`); mutate(); } catch (e) { pushNotification('error', `Failed to deactivate ${deaCase.name}.`); } setSelectedCase([]); } function enableDeactivateCaseModal() { setShowDeactivateModal(true); } function disableDeactivateCaseModal() { setShowDeactivateModal(false); } function enableActivateCaseModal() { setShowActivateModal(true); } function disableActivateCaseModal() { setShowActivateModal(false); } function noUserCaseStatusUpdatePermission(deaCase: DeaCaseDTO): boolean { if (!deaCase.actions) { return true; } return !deaCase.actions.includes(CaseAction.UPDATE_CASE_STATUS); } function statusCell(deaCase: DeaCaseDTO) { if (deaCase.status == CaseStatus.ACTIVE) { return {caseStatusLabels.active}; } else { return {caseStatusLabels.inactive}; } } function deactivateCaseModal() { return ( } header={ selectedCase.length === 0 ? 'No cases selected' : caseListLabels.deactivateCaseModalLabel(selectedCase[0].name) } > {caseListLabels.deactivateCaseModalMessage} {canDeleteCaseFiles(availableEndpoints.data) && ( setDeleteFiles(detail.checked)} checked={deleteFiles}> {caseListLabels.deleteFilesLabel} )} ); } function nameCell(deaCase: DeaCaseDTO) { return ( { e.preventDefault(); return router.push(`/${props.detailPage}?caseId=${e.detail.href}`); }} > {deaCase.name} ); } return ( setSelectedCase(detail.selectedItems)} selectedItems={selectedCase} selectionType="single" isItemDisabled={noUserCaseStatusUpdatePermission} trackBy="ulid" loading={isLoading} variant="full-page" items={items} loadingText={caseListLabels.loading} resizableColumns={true} empty={TableEmptyDisplay(caseListLabels.noCasesLabel, caseListLabels.noDisplayLabel)} header={ {deactivateCaseModal()} {canUpdateCaseStatus(availableEndpoints.data) && ( )} {canUpdateCaseStatus(availableEndpoints.data) && ( )} {props.canCreate && ( )} } totalItems={data} /> } columnDefinitions={[ { id: 'name', header: commonTableLabels.caseNameHeader, cell: nameCell, width: 350, minWidth: 220, sortingField: 'name', }, { id: 'objectCount', header: commonTableLabels.objectCounterHeader, cell: (e) => e.objectCount, width: 220, minWidth: 165, sortingField: 'objectCount', }, { id: 'totalSize', header: commonTableLabels.totalSize, cell: (e) => formatFileSize(e.totalSizeBytes), width: 220, minWidth: 165, sortingField: 'totalSizeBytes', }, { id: 'created', header: commonTableLabels.creationDateHeader, cell: (e) => formatDateFromISOString(e.created), width: 200, minWidth: 165, sortingField: 'created', }, { id: 'status', header: commonTableLabels.statusHeader, cell: statusCell, width: 200, minWidth: 165, sortingField: 'status', }, ]} filter={ } pagination={} /> ); } const getFilterCounterText = (count: number | undefined): string => `${count} ${count === 1 ? 'match' : 'matches'}`; export default CaseTable;