import { AxiosResponse } from 'axios'; import { FormEventHandler, MouseEventHandler, useEffect, useState, } from 'react'; import { Box, Checkbox, checkboxClasses } from '@mui/material'; import API_BASE_URL from '../../lib/consts/API_BASE_URL'; import { GREY, RED, TEXT } from '../../lib/consts/DEFAULT_THEME'; import ConfirmDialog from '../ConfirmDialog'; import ContainedButton from '../ContainedButton'; import FileInfo from './FileInfo'; import Spinner from '../Spinner'; import fetchJSON from '../../lib/fetchers/fetchJSON'; import mainAxiosInstance from '../../lib/singletons/mainAxiosInstance'; type ReducedFileLocation = Partial< Pick >; type EditRequestContent = Partial< Pick > & { fileLocations: ReducedFileLocation[]; }; type FileEditProps = { filesOverview: FileOverviewMetadata[]; onEditFilesComplete?: () => void; onPurgeFilesComplete?: () => void; }; type FileToEdit = FileDetailMetadata & { dataIncompleteError?: unknown; isSelected?: boolean; }; const FILE_EDIT_FORM_DEFAULT_PROPS = { onEditFilesComplete: undefined, onPurgeFilesComplete: undefined, }; const FileEditForm = ( { filesOverview, onEditFilesComplete, onPurgeFilesComplete, }: FileEditProps = FILE_EDIT_FORM_DEFAULT_PROPS as FileEditProps, ): JSX.Element => { const [editRequestContents, setEditRequestContents] = useState< EditRequestContent[] >([]); const [filesToEdit, setFilesToEdit] = useState([]); const [isLoadingFilesToEdit, setIsLoadingFilesToEdit] = useState(false); const [isOpenPurgeConfirmDialog, setIsOpenConfirmPurgeDialog] = useState(false); const [selectedFilesCount, setSelectedFilesCount] = useState(0); const purgeButtonStyleOverride = { backgroundColor: RED, color: TEXT, '&:hover': { backgroundColor: RED }, }; const generateFileInfoChangeHandler = (fileIndex: number): FileInfoChangeHandler => (inputValues, { fileLocationIndex } = {}) => { if (fileLocationIndex !== undefined) { editRequestContents[fileIndex].fileLocations[fileLocationIndex] = { ...editRequestContents[fileIndex].fileLocations[fileLocationIndex], ...inputValues, }; } else { editRequestContents[fileIndex] = { ...editRequestContents[fileIndex], ...inputValues, }; } }; const editFiles: FormEventHandler = (event) => { event.preventDefault(); setIsLoadingFilesToEdit(true); const editPromises = editRequestContents.reduce[]>( ( reducedEditPromises, { fileLocations, fileName, fileType, fileUUID }, ) => { const editRequestContent: Partial = {}; if (fileName !== undefined) { editRequestContent.fileName = fileName; } if (fileType !== undefined) { editRequestContent.fileType = fileType; } const changedFileLocations = fileLocations.reduce< ReducedFileLocation[] >( ( reducedFileLocations, { fileLocationUUID, isFileLocationActive }, ) => { if (isFileLocationActive !== undefined) { reducedFileLocations.push({ fileLocationUUID, isFileLocationActive, }); } return reducedFileLocations; }, [], ); if (changedFileLocations.length > 0) { editRequestContent.fileLocations = changedFileLocations; } const stringEditFileRequestContent = JSON.stringify(editRequestContent); if (stringEditFileRequestContent !== '{}') { reducedEditPromises.push( mainAxiosInstance.put( `/files/${fileUUID}`, stringEditFileRequestContent, { headers: { 'Content-Type': 'application/json' }, }, ), ); } return reducedEditPromises; }, [], ); Promise.all(editPromises) .then(() => { setIsLoadingFilesToEdit(false); }) .then(onEditFilesComplete); }; const purgeFiles: MouseEventHandler = () => { setIsOpenConfirmPurgeDialog(false); setIsLoadingFilesToEdit(true); const purgePromises = filesToEdit .filter(({ isSelected }) => isSelected) .map(({ fileUUID }) => mainAxiosInstance.delete(`/files/${fileUUID}`)); Promise.all(purgePromises) .then(() => { setIsLoadingFilesToEdit(false); }) .then(onPurgeFilesComplete); }; const cancelPurge: MouseEventHandler = () => { setIsOpenConfirmPurgeDialog(false); }; const confirmPurge: MouseEventHandler = () => { // We need this local variable because setState functions are async; the // changes won't reflect until the next render cycle. // In this case, the user would have to click on the purge button twice to // trigger the confirmation dialog without using this local variable. const localSelectedFilesCount = filesToEdit.filter( ({ isSelected }) => isSelected, ).length; setSelectedFilesCount(localSelectedFilesCount); if (localSelectedFilesCount > 0) { setIsOpenConfirmPurgeDialog(true); } }; useEffect(() => { setIsLoadingFilesToEdit(true); Promise.all( filesOverview.map(async (fileOverview: FileOverviewMetadata) => { const fileToEdit: FileToEdit = { ...fileOverview, fileLocations: [], }; try { const data = await fetchJSON( `${API_BASE_URL}/files/${fileOverview.fileUUID}`, ); fileToEdit.fileLocations = data.map( ([ , , , , , fileLocationUUID, fileLocationActive, anvilUUID, anvilName, anvilDescription, ]) => ({ anvilDescription, anvilName, anvilUUID, fileLocationUUID, isFileLocationActive: parseInt(fileLocationActive, 10) === 1, }), ); } catch (fetchError) { fileToEdit.dataIncompleteError = fetchError; } return fileToEdit; }), ).then((fetchedFilesDetail) => { setFilesToEdit(fetchedFilesDetail); const initialEditRequestContents: EditRequestContent[] = []; for ( let fileIndex = 0; fileIndex < fetchedFilesDetail.length; fileIndex += 1 ) { const fetchedFileDetail = fetchedFilesDetail[fileIndex]; initialEditRequestContents.push({ fileUUID: fetchedFileDetail.fileUUID, fileLocations: fetchedFileDetail.fileLocations.map( ({ fileLocationUUID }) => ({ fileLocationUUID, }), ), }); } setEditRequestContents(initialEditRequestContents); setIsLoadingFilesToEdit(false); }); }, [filesOverview]); return ( <> {isLoadingFilesToEdit ? ( ) : (
:not(:first-child)': { marginTop: '1em' }, }} > {filesToEdit.map( ({ fileName, fileLocations, fileType, fileUUID }, fileIndex) => ( :last-child': { flexGrow: 1, }, }} > { filesToEdit[fileIndex].isSelected = checked; }} sx={{ color: GREY, [`&.${checkboxClasses.checked}`]: { color: TEXT, }, }} /> ), )} {filesToEdit.length > 0 && ( :not(:last-child)': { marginRight: '.5em', }, }} > Purge Update )} )} ); }; FileEditForm.defaultProps = FILE_EDIT_FORM_DEFAULT_PROPS; export default FileEditForm;