Files
automatisch/packages/web/src/components/Folders/index.jsx
2025-05-19 11:28:57 +02:00

231 lines
6.9 KiB
JavaScript

import * as React from 'react';
import { Link, useSearchParams, useNavigate } from 'react-router-dom';
import Box from '@mui/material/Box';
import Stack from '@mui/material/Stack';
import Card from '@mui/material/Card';
import IconButton from '@mui/material/IconButton';
import DeleteIcon from '@mui/icons-material/Delete';
import EditIcon from '@mui/icons-material/Edit';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import ListItemButton from '@mui/material/ListItemButton';
import ListItemText from '@mui/material/ListItemText';
import Divider from '@mui/material/Divider';
import AddIcon from '@mui/icons-material/Add';
import ViewListIcon from '@mui/icons-material/ViewList';
import Inventory2Icon from '@mui/icons-material/Inventory2';
import { getGeneralErrorMessage } from 'helpers/errors';
import ConfirmationDialog from 'components/ConfirmationDialog';
import CreateFolderDialog from 'components/CreateFolderDialog';
import EditFolderDialog from 'components/EditFolderDialog';
import useFormatMessage from 'hooks/useFormatMessage';
import useFolders from 'hooks/useFolders';
import useDeleteFolder from 'hooks/useDeleteFolder';
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
import useFlowFilters from 'hooks/useFlowFilters';
import { ListItemIcon } from './style';
export default function Folders() {
const [showEditFolderDialog, setShowEditFolderDialog] = React.useState(false);
const formatMessage = useFormatMessage();
const [searchParams] = useSearchParams();
const navigate = useNavigate();
const { data: folders } = useFolders();
const enqueueSnackbar = useEnqueueSnackbar();
const { enhanceExistingSearchParams } = useFlowFilters();
const {
mutateAsync: deleteFolder,
error: deleteFolderError,
reset: resetDeleteFolder,
} = useDeleteFolder();
const [showCreateFolderDialog, setShowCreateFolderDialog] =
React.useState(false);
const [showDeleteFolderDialog, setShowDeleteFolderDialog] =
React.useState(false);
const selectedFolderId = searchParams.get('folderId');
const selectedFolder = folders?.data?.find(
(folder) => folder.id === selectedFolderId,
);
const allFlowsFolder = enhanceExistingSearchParams('folderId', undefined);
const unassignedFlowsFolder = enhanceExistingSearchParams('folderId', 'null');
const allFlowsFolderSelected = selectedFolderId === null;
const unassignedFlowsFolderSelected = selectedFolderId === 'null'; // intendedly stringified
const generalErrorMessage = getGeneralErrorMessage({
error: deleteFolderError,
fallbackMessage: formatMessage('genericError'),
});
const handleDeleteFolderConfirmation = async () => {
await deleteFolder(selectedFolderId);
setShowDeleteFolderDialog(false);
navigate({ search: allFlowsFolder });
enqueueSnackbar(formatMessage('folders.successfullyDeleted'), {
variant: 'success',
SnackbarProps: {
'data-test': 'snackbar-delete-folder-success',
},
});
};
const handleDeleteFolderClose = async () => {
setShowDeleteFolderDialog(false);
resetDeleteFolder();
};
const getFolderSearchParams = (folderId) => {
return enhanceExistingSearchParams('folderId', folderId).toString();
};
const generateFolderItem = (folder) => {
if (folder.id === selectedFolderId) {
return generateListItem(folder);
}
return generateListItemButton(folder);
};
const generateListItem = (folder) => {
return (
<ListItem
key={folder.id}
disablePadding
secondaryAction={
<Stack direction="row" gap={1}>
<IconButton
data-test="edit-folder"
edge="end"
aria-label="edit"
onClick={() => setShowEditFolderDialog(true)}
>
<EditIcon />
</IconButton>
<IconButton
data-test="delete-folder"
edge="end"
aria-label="delete"
onClick={() => setShowDeleteFolderDialog(true)}
>
<DeleteIcon />
</IconButton>
</Stack>
}
>
<ListItemButton
selected={true}
disableRipple
sx={{ pointerEvents: 'none' }}
>
<ListItemText primary={folder.name} />
</ListItemButton>
</ListItem>
);
};
const generateListItemButton = (folder) => {
return (
<ListItemButton
key={folder.id}
component={Link}
to={{ search: getFolderSearchParams(folder.id) }}
>
<ListItemText primary={folder.name} />
</ListItemButton>
);
};
return (
<>
<Box component={Card}>
<List component="nav" aria-label="static folders">
<ListItemButton
data-test="all-flows-folder"
component={Link}
to={{ search: allFlowsFolder }}
selected={allFlowsFolderSelected}
>
<ListItemIcon>
<ViewListIcon />
</ListItemIcon>
<ListItemText primary={formatMessage('folders.allFlows')} />
</ListItemButton>
<ListItemButton
data-test="uncategorized-flows-folder"
component={Link}
to={{ search: unassignedFlowsFolder }}
selected={unassignedFlowsFolderSelected}
>
<ListItemIcon>
<Inventory2Icon />
</ListItemIcon>
<ListItemText primary={formatMessage('folders.unassignedFlows')} />
</ListItemButton>
</List>
<Divider />
<List
component="nav"
aria-label="user folders"
data-test="user-folders"
>
{folders?.data?.map((folder) => generateFolderItem(folder))}
<ListItemButton
data-test="add-folder-button"
onClick={() => setShowCreateFolderDialog(true)}
>
<ListItemIcon>
<AddIcon />
</ListItemIcon>
<ListItemText primary={formatMessage('folders.createNew')} />
</ListItemButton>
</List>
</Box>
{showCreateFolderDialog && (
<CreateFolderDialog onClose={() => setShowCreateFolderDialog(false)} />
)}
{selectedFolder && showEditFolderDialog && (
<EditFolderDialog
folder={selectedFolder}
onClose={() => setShowEditFolderDialog(false)}
/>
)}
{selectedFolder && showDeleteFolderDialog && (
<ConfirmationDialog
title={formatMessage('deleteFolderDialog.title')}
description={formatMessage('deleteFolderDialog.description')}
onClose={handleDeleteFolderClose}
onConfirm={handleDeleteFolderConfirmation}
cancelButtonChildren={formatMessage('deleteFolderDialog.cancel')}
confirmButtonChildren={formatMessage('deleteFolderDialog.confirm')}
data-test="delete-folder-modal"
errorMessage={generalErrorMessage}
/>
)}
</>
);
}