feat(web): introduce folders to organize flows
This commit is contained in:
@@ -19,7 +19,9 @@ function ConfirmationDialog(props) {
|
||||
open = true,
|
||||
errorMessage,
|
||||
} = props;
|
||||
|
||||
const dataTest = props['data-test'];
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={onClose} data-test={dataTest}>
|
||||
{title && <DialogTitle>{title}</DialogTitle>}
|
||||
@@ -46,6 +48,7 @@ function ConfirmationDialog(props) {
|
||||
</Button>
|
||||
)}
|
||||
</DialogActions>
|
||||
|
||||
{errorMessage && (
|
||||
<Alert data-test="confirmation-dialog-error-alert" severity="error">
|
||||
{errorMessage}
|
||||
|
||||
106
packages/web/src/components/CreateFolderDialog/index.jsx
Normal file
106
packages/web/src/components/CreateFolderDialog/index.jsx
Normal file
@@ -0,0 +1,106 @@
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import Alert from '@mui/material/Alert';
|
||||
import Button from '@mui/material/Button';
|
||||
import Dialog from '@mui/material/Dialog';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import DialogContentText from '@mui/material/DialogContentText';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import * as React from 'react';
|
||||
|
||||
import { getUnifiedErrorMessage } from 'helpers/errors';
|
||||
import useCreateFolder from 'hooks/useCreateFolder';
|
||||
import useFormatMessage from 'hooks/useFormatMessage';
|
||||
|
||||
export default function CreateFolderDialog(props) {
|
||||
const { open = true, onClose } = props;
|
||||
|
||||
const [folderName, setFolderName] = React.useState('');
|
||||
const formatMessage = useFormatMessage();
|
||||
|
||||
const { mutate: createFolder, error, isError, isSuccess } = useCreateFolder();
|
||||
|
||||
const handleCreateFolder = () => {
|
||||
createFolder({ name: folderName });
|
||||
|
||||
setFolderName('');
|
||||
};
|
||||
|
||||
const handleTextFieldChange = (event) => {
|
||||
setFolderName(event.target.value);
|
||||
};
|
||||
|
||||
const handleTextFieldKeyDown = (event) => {
|
||||
if (event.key === 'Enter') {
|
||||
handleCreateFolder();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={onClose} data-test="create-folder-dialog">
|
||||
<DialogTitle>{formatMessage('createFolderDialog.title')}</DialogTitle>
|
||||
|
||||
<IconButton
|
||||
aria-label="close"
|
||||
onClick={onClose}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
right: 8,
|
||||
top: 8,
|
||||
color: (theme) => theme.palette.grey[500],
|
||||
}}
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
{formatMessage('createFolderDialog.description')}
|
||||
|
||||
<TextField
|
||||
sx={{ mt: 2 }}
|
||||
value={folderName}
|
||||
onKeyDown={handleTextFieldKeyDown}
|
||||
onChange={handleTextFieldChange}
|
||||
label={formatMessage('createFolderDialog.folderNameInputLabel')}
|
||||
fullWidth
|
||||
/>
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
|
||||
<DialogActions sx={{ mb: 1 }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={handleCreateFolder}
|
||||
data-test="create-folder-dialog-create-button"
|
||||
startIcon={<AddIcon />}
|
||||
>
|
||||
{formatMessage('createFolderDialog.create')}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
|
||||
{isError && (
|
||||
<Alert
|
||||
data-test="create-folder-dialog-generic-error-alert"
|
||||
severity="error"
|
||||
sx={{ whiteSpace: 'pre-line' }}
|
||||
>
|
||||
{getUnifiedErrorMessage(error?.response?.data?.errors) ||
|
||||
formatMessage('genericError')}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{isSuccess && (
|
||||
<Alert
|
||||
data-test="create-folder-dialog-success-alert"
|
||||
severity="success"
|
||||
>
|
||||
{formatMessage('createFolderDialog.successfullyCreatedFolder')}
|
||||
</Alert>
|
||||
)}
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
107
packages/web/src/components/EditFolderDialog/index.jsx
Normal file
107
packages/web/src/components/EditFolderDialog/index.jsx
Normal file
@@ -0,0 +1,107 @@
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import SaveIcon from '@mui/icons-material/Save';
|
||||
import Alert from '@mui/material/Alert';
|
||||
import Button from '@mui/material/Button';
|
||||
import Dialog from '@mui/material/Dialog';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import DialogContentText from '@mui/material/DialogContentText';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import * as React from 'react';
|
||||
|
||||
import { getUnifiedErrorMessage } from 'helpers/errors';
|
||||
import useFormatMessage from 'hooks/useFormatMessage';
|
||||
import useUpdateFolder from 'hooks/useUpdateFolder';
|
||||
|
||||
export default function EditFolderDialog(props) {
|
||||
const { open = true, onClose, folder = {} } = props;
|
||||
|
||||
const [folderName, setFolderName] = React.useState(folder.name);
|
||||
const formatMessage = useFormatMessage();
|
||||
|
||||
const {
|
||||
mutate: updateFolder,
|
||||
error,
|
||||
isError,
|
||||
isSuccess,
|
||||
} = useUpdateFolder(folder.id);
|
||||
|
||||
const handleUpdateFolder = () => {
|
||||
updateFolder({ name: folderName });
|
||||
};
|
||||
|
||||
const handleTextFieldChange = (event) => {
|
||||
setFolderName(event.target.value);
|
||||
};
|
||||
|
||||
const handleTextFieldKeyDown = (event) => {
|
||||
if (event.key === 'Enter') {
|
||||
handleUpdateFolder();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={onClose} data-test="edit-folder-dialog">
|
||||
<DialogTitle>{formatMessage('editFolderDialog.title')}</DialogTitle>
|
||||
|
||||
<IconButton
|
||||
aria-label="close"
|
||||
onClick={onClose}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
right: 8,
|
||||
top: 8,
|
||||
color: (theme) => theme.palette.grey[500],
|
||||
}}
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
{formatMessage('editFolderDialog.description')}
|
||||
|
||||
<TextField
|
||||
sx={{ mt: 2 }}
|
||||
value={folderName}
|
||||
onKeyDown={handleTextFieldKeyDown}
|
||||
onChange={handleTextFieldChange}
|
||||
label={formatMessage('editFolderDialog.folderNameInputLabel')}
|
||||
fullWidth
|
||||
/>
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
|
||||
<DialogActions sx={{ mb: 1 }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={handleUpdateFolder}
|
||||
data-test="edit-folder-dialog-update-button"
|
||||
startIcon={<SaveIcon />}
|
||||
Save
|
||||
>
|
||||
{formatMessage('editFolderDialog.update')}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
|
||||
{isError && (
|
||||
<Alert
|
||||
data-test="edit-folder-dialog-generic-error-alert"
|
||||
severity="error"
|
||||
sx={{ whiteSpace: 'pre-line' }}
|
||||
>
|
||||
{getUnifiedErrorMessage(error?.response?.data?.errors) ||
|
||||
formatMessage('genericError')}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{isSuccess && (
|
||||
<Alert data-test="edit-folder-dialog-success-alert" severity="success">
|
||||
{formatMessage('editFolderDialog.successfullyUpdatedFolder')}
|
||||
</Alert>
|
||||
)}
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -1,22 +1,25 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import Menu from '@mui/material/Menu';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
|
||||
import * as React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import Can from 'components/Can';
|
||||
import FlowFolderChangeDialog from 'components/FlowFolderChangeDialog';
|
||||
import * as URLS from 'config/urls';
|
||||
import useFormatMessage from 'hooks/useFormatMessage';
|
||||
import useDuplicateFlow from 'hooks/useDuplicateFlow';
|
||||
import useDeleteFlow from 'hooks/useDeleteFlow';
|
||||
import useExportFlow from 'hooks/useExportFlow';
|
||||
import useDownloadJsonAsFile from 'hooks/useDownloadJsonAsFile';
|
||||
import useDuplicateFlow from 'hooks/useDuplicateFlow';
|
||||
import useExportFlow from 'hooks/useExportFlow';
|
||||
import useFormatMessage from 'hooks/useFormatMessage';
|
||||
|
||||
function ContextMenu(props) {
|
||||
const { flowId, onClose, anchorEl, onDuplicateFlow, appKey } = props;
|
||||
const [showFlowFolderChangeDialog, setShowFlowFolderChangeDialog] =
|
||||
React.useState(false);
|
||||
const enqueueSnackbar = useEnqueueSnackbar();
|
||||
const formatMessage = useFormatMessage();
|
||||
const queryClient = useQueryClient();
|
||||
@@ -91,45 +94,67 @@ function ContextMenu(props) {
|
||||
onClose();
|
||||
}, [exportFlow, downloadJsonAsFile, enqueueSnackbar, formatMessage, onClose]);
|
||||
|
||||
const onFlowFolderUpdate = React.useCallback(() => {
|
||||
setShowFlowFolderChangeDialog(true);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Menu
|
||||
open={true}
|
||||
onClose={onClose}
|
||||
hideBackdrop={false}
|
||||
anchorEl={anchorEl}
|
||||
>
|
||||
<Can I="read" a="Flow" passThrough>
|
||||
{(allowed) => (
|
||||
<MenuItem disabled={!allowed} component={Link} to={URLS.FLOW(flowId)}>
|
||||
{formatMessage('flow.view')}
|
||||
</MenuItem>
|
||||
)}
|
||||
</Can>
|
||||
<>
|
||||
<Menu
|
||||
open={true}
|
||||
onClose={onClose}
|
||||
hideBackdrop={false}
|
||||
anchorEl={anchorEl}
|
||||
>
|
||||
<Can I="read" a="Flow" passThrough>
|
||||
{(allowed) => (
|
||||
<MenuItem
|
||||
disabled={!allowed}
|
||||
component={Link}
|
||||
to={URLS.FLOW(flowId)}
|
||||
>
|
||||
{formatMessage('flow.view')}
|
||||
</MenuItem>
|
||||
)}
|
||||
</Can>
|
||||
|
||||
<Can I="create" a="Flow" passThrough>
|
||||
{(allowed) => (
|
||||
<MenuItem disabled={!allowed} onClick={onFlowDuplicate}>
|
||||
{formatMessage('flow.duplicate')}
|
||||
</MenuItem>
|
||||
)}
|
||||
</Can>
|
||||
<Can I="create" a="Flow" passThrough>
|
||||
{(allowed) => (
|
||||
<MenuItem disabled={!allowed} onClick={onFlowDuplicate}>
|
||||
{formatMessage('flow.duplicate')}
|
||||
</MenuItem>
|
||||
)}
|
||||
</Can>
|
||||
|
||||
<Can I="read" a="Flow" passThrough>
|
||||
{(allowed) => (
|
||||
<MenuItem disabled={!allowed} onClick={onFlowExport}>
|
||||
{formatMessage('flow.export')}
|
||||
</MenuItem>
|
||||
)}
|
||||
</Can>
|
||||
<Can I="update" a="Flow" passThrough>
|
||||
{(allowed) => (
|
||||
<MenuItem disabled={!allowed} onClick={onFlowFolderUpdate}>
|
||||
{formatMessage('flow.moveTo')}
|
||||
</MenuItem>
|
||||
)}
|
||||
</Can>
|
||||
|
||||
<Can I="delete" a="Flow" passThrough>
|
||||
{(allowed) => (
|
||||
<MenuItem disabled={!allowed} onClick={onFlowDelete}>
|
||||
{formatMessage('flow.delete')}
|
||||
</MenuItem>
|
||||
)}
|
||||
</Can>
|
||||
</Menu>
|
||||
<Can I="read" a="Flow" passThrough>
|
||||
{(allowed) => (
|
||||
<MenuItem disabled={!allowed} onClick={onFlowExport}>
|
||||
{formatMessage('flow.export')}
|
||||
</MenuItem>
|
||||
)}
|
||||
</Can>
|
||||
|
||||
<Can I="delete" a="Flow" passThrough>
|
||||
{(allowed) => (
|
||||
<MenuItem disabled={!allowed} onClick={onFlowDelete}>
|
||||
{formatMessage('flow.delete')}
|
||||
</MenuItem>
|
||||
)}
|
||||
</Can>
|
||||
</Menu>
|
||||
|
||||
{showFlowFolderChangeDialog && (
|
||||
<FlowFolderChangeDialog flowId={flowId} onClose={onClose} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
145
packages/web/src/components/FlowFolderChangeDialog/index.jsx
Normal file
145
packages/web/src/components/FlowFolderChangeDialog/index.jsx
Normal file
@@ -0,0 +1,145 @@
|
||||
import Alert from '@mui/material/Alert';
|
||||
import Dialog from '@mui/material/Dialog';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import LoadingButton from '@mui/lab/LoadingButton';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import DialogContentText from '@mui/material/DialogContentText';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
import FormControl from '@mui/material/FormControl';
|
||||
import CircularProgress from '@mui/material/CircularProgress';
|
||||
import Autocomplete from '@mui/material/Autocomplete';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import * as React from 'react';
|
||||
|
||||
import useFolders from 'hooks/useFolders';
|
||||
import useFormatMessage from 'hooks/useFormatMessage';
|
||||
import useFlowFolder from 'hooks/useFlowFolder';
|
||||
import useUpdateFlowFolder from 'hooks/useUpdateFlowFolder';
|
||||
|
||||
function FlowFolderChangeDialog(props) {
|
||||
const { flowId, onClose, open = true } = props;
|
||||
|
||||
const formatMessage = useFormatMessage();
|
||||
const { data: folders, isLoading: isFoldersLoading } = useFolders();
|
||||
const { data: flowFolder, isLoading: isFlowFolderLoading } =
|
||||
useFlowFolder(flowId);
|
||||
|
||||
const [selectedFolder, setSelectedFolder] = React.useState(null);
|
||||
|
||||
const uncategorizedFolder = { id: null, name: 'Uncategorized' };
|
||||
|
||||
const {
|
||||
mutate: updateFlowFolder,
|
||||
isSuccess,
|
||||
isPending: isUpdateFlowFolderPending,
|
||||
error: createUpdateFlowFolderError,
|
||||
} = useUpdateFlowFolder(flowId);
|
||||
|
||||
const handleChange = (event, newValue) => {
|
||||
setSelectedFolder(newValue ? newValue.id : null);
|
||||
};
|
||||
|
||||
const handleConfirm = () => {
|
||||
updateFlowFolder(selectedFolder);
|
||||
};
|
||||
|
||||
React.useEffect(
|
||||
function updateInitialSelectedFolder() {
|
||||
if (!flowFolder) return;
|
||||
|
||||
setSelectedFolder(flowFolder.data?.id || null);
|
||||
},
|
||||
[flowFolder],
|
||||
);
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={onClose}>
|
||||
<DialogTitle>{formatMessage('flowFolderChangeDialog.title')}</DialogTitle>
|
||||
|
||||
<IconButton
|
||||
aria-label="close"
|
||||
onClick={onClose}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
right: 8,
|
||||
top: 8,
|
||||
color: (theme) => theme.palette.grey[500],
|
||||
}}
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
|
||||
<DialogContent>
|
||||
<DialogContentText sx={{ mb: 2 }}>
|
||||
{formatMessage('flowFolderChangeDialog.description')}
|
||||
</DialogContentText>
|
||||
|
||||
<FormControl fullWidth>
|
||||
<Autocomplete
|
||||
value={
|
||||
folders?.data.find((folder) => folder.id === selectedFolder) ||
|
||||
uncategorizedFolder
|
||||
}
|
||||
disableClearable={true}
|
||||
onChange={handleChange}
|
||||
options={[uncategorizedFolder, ...(folders?.data || [])]}
|
||||
isOptionEqualToValue={(option, value) => option.id === value.id}
|
||||
getOptionLabel={(option) => option.name}
|
||||
loading={isFoldersLoading || isFlowFolderLoading}
|
||||
disabled={isFoldersLoading || isFlowFolderLoading}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
label={formatMessage('flowFolderChangeDialog.folderInputLabel')}
|
||||
InputProps={{
|
||||
...params.InputProps,
|
||||
endAdornment: (
|
||||
<>
|
||||
{(isFoldersLoading || isFlowFolderLoading) && (
|
||||
<CircularProgress color="inherit" size={20} />
|
||||
)}
|
||||
|
||||
{params.InputProps.endAdornment}
|
||||
</>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</FormControl>
|
||||
</DialogContent>
|
||||
|
||||
<DialogActions>
|
||||
<LoadingButton
|
||||
onClick={handleConfirm}
|
||||
data-test="flow-folder-change-dialog-confirm-button"
|
||||
loading={isUpdateFlowFolderPending}
|
||||
>
|
||||
{formatMessage('flowFolderChangeDialog.confirm')}
|
||||
</LoadingButton>
|
||||
</DialogActions>
|
||||
|
||||
{createUpdateFlowFolderError && (
|
||||
<Alert
|
||||
data-test="flow-folder-change-dialog-error-alert"
|
||||
severity="error"
|
||||
>
|
||||
{createUpdateFlowFolderError.message}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{isSuccess && (
|
||||
<Alert
|
||||
data-test="flow-folder-change-dialog-success-alert"
|
||||
severity="success"
|
||||
>
|
||||
{formatMessage('flowFolderChangeDialog.successfullyUpdatedFolder')}
|
||||
</Alert>
|
||||
)}
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export default FlowFolderChangeDialog;
|
||||
@@ -0,0 +1,6 @@
|
||||
import { styled } from '@mui/material/styles';
|
||||
import MuiListItemIcon from '@mui/material/ListItemIcon';
|
||||
|
||||
export const ListItemIcon = styled(MuiListItemIcon)(({ theme }) => ({
|
||||
minWidth: theme.spacing(4),
|
||||
}));
|
||||
@@ -116,6 +116,7 @@ function FlowRow(props) {
|
||||
{anchorEl && (
|
||||
<FlowContextMenu
|
||||
flowId={flow.id}
|
||||
folderId={flow.folder?.id}
|
||||
onClose={handleClose}
|
||||
anchorEl={anchorEl}
|
||||
onDuplicateFlow={onDuplicateFlow}
|
||||
|
||||
200
packages/web/src/components/Folders/index.jsx
Normal file
200
packages/web/src/components/Folders/index.jsx
Normal file
@@ -0,0 +1,200 @@
|
||||
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 { 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 { mutateAsync: deleteFolder, error: deleteFolderError } =
|
||||
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 = new URLSearchParams().toString();
|
||||
const unassignedFlowsFolder = new URLSearchParams('folderId=null').toString();
|
||||
|
||||
const allFlowsFolderSelected = selectedFolderId === null;
|
||||
const unassignedFlowsFolderSelected = selectedFolderId === 'null'; // intendedly stringified
|
||||
|
||||
const generalErrorMessage = getGeneralErrorMessage({
|
||||
error: deleteFolderError,
|
||||
fallbackMessage: formatMessage('genericError'),
|
||||
});
|
||||
|
||||
const handleDeleteFolderConfirmation = async () => {
|
||||
await deleteFolder(selectedFolderId);
|
||||
|
||||
navigate({ search: allFlowsFolder });
|
||||
};
|
||||
|
||||
const getFolderSearchParams = (folderId) => {
|
||||
const searchParams = new URLSearchParams(`folderId=${folderId}`);
|
||||
|
||||
return searchParams.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
|
||||
edge="end"
|
||||
aria-label="edit"
|
||||
onClick={() => setShowEditFolderDialog(true)}
|
||||
>
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
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}
|
||||
// sx={{ width: '100%', maxWidth: 360, bgcolor: 'background.paper' }}
|
||||
>
|
||||
<List component="nav" aria-label="static folders">
|
||||
<ListItemButton
|
||||
component={Link}
|
||||
to={{ search: allFlowsFolder }}
|
||||
selected={allFlowsFolderSelected}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<ViewListIcon />
|
||||
</ListItemIcon>
|
||||
|
||||
<ListItemText primary={formatMessage('folders.allFlows')} />
|
||||
</ListItemButton>
|
||||
|
||||
<ListItemButton
|
||||
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">
|
||||
{folders?.data?.map((folder) => generateFolderItem(folder))}
|
||||
|
||||
<ListItemButton 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={() => setShowDeleteFolderDialog(false)}
|
||||
onConfirm={handleDeleteFolderConfirmation}
|
||||
cancelButtonChildren={formatMessage('deleteFolderDialog.cancel')}
|
||||
confirmButtonChildren={formatMessage('deleteFolderDialog.confirm')}
|
||||
errorMessage={generalErrorMessage}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
6
packages/web/src/components/Folders/style.js
Normal file
6
packages/web/src/components/Folders/style.js
Normal file
@@ -0,0 +1,6 @@
|
||||
import { styled } from '@mui/material/styles';
|
||||
import MuiListItemIcon from '@mui/material/ListItemIcon';
|
||||
|
||||
export const ListItemIcon = styled(MuiListItemIcon)(({ theme }) => ({
|
||||
minWidth: theme.spacing(4),
|
||||
}));
|
||||
Reference in New Issue
Block a user