diff --git a/frontend/src/component/common/FeatureArchiveDialog/FeatureArchiveDialog.tsx b/frontend/src/component/common/FeatureArchiveDialog/FeatureArchiveDialog.tsx index 25e3c5c972..325f529251 100644 --- a/frontend/src/component/common/FeatureArchiveDialog/FeatureArchiveDialog.tsx +++ b/frontend/src/component/common/FeatureArchiveDialog/FeatureArchiveDialog.tsx @@ -3,13 +3,14 @@ import { Dialogue } from 'component/common/Dialogue/Dialogue'; import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi'; import useToast from 'hooks/useToast'; import { formatUnknownError } from 'utils/formatUnknownError'; +import { ConditionallyRender } from '../ConditionallyRender/ConditionallyRender'; interface IFeatureArchiveDialogProps { isOpen: boolean; onConfirm: () => void; onClose: () => void; projectId: string; - featureId: string; + featureIds: string[]; } export const FeatureArchiveDialog: VFC = ({ @@ -17,14 +18,15 @@ export const FeatureArchiveDialog: VFC = ({ onClose, onConfirm, projectId, - featureId, + featureIds, }) => { const { archiveFeatureToggle } = useFeatureApi(); const { setToastData, setToastApiError } = useToast(); + const isBulkArchive = featureIds?.length > 1; const archiveToggle = async () => { try { - await archiveFeatureToggle(projectId, featureId); + await archiveFeatureToggle(projectId, featureIds[0]); setToastData({ text: 'Your feature toggle has been archived', type: 'success', @@ -38,16 +40,69 @@ export const FeatureArchiveDialog: VFC = ({ } }; + const archiveToggles = async () => { + try { + // TODO: bulk archive + await Promise.allSettled( + featureIds.map(id => { + archiveFeatureToggle(projectId, id); + }) + ); + setToastData({ + text: 'Selected feature toggles have been archived', + type: 'success', + title: 'Feature toggles archived', + }); + onConfirm(); + onClose(); + } catch (error: unknown) { + setToastApiError(formatUnknownError(error)); + onClose(); + } + }; + return ( archiveToggle()} + onClick={isBulkArchive ? archiveToggles : archiveToggle} open={isOpen} onClose={onClose} - primaryButtonText="Archive toggle" + primaryButtonText={ + isBulkArchive ? 'Archive toggles' : 'Archive toggle' + } secondaryButtonText="Cancel" - title="Archive feature toggle" + title={ + isBulkArchive + ? 'Archive feature toggles' + : 'Archive feature toggle' + } > - Are you sure you want to archive this feature toggle? + +

+ Are you sure you want to archive{' '} + {featureIds?.length} feature + toggles? +

+ + {featureIds?.map(id => ( +
  • {id}
  • + ))} + + } + /> + + } + elseShow={ +

    + Are you sure you want to archive these feature toggles? +

    + } + />
    ); }; diff --git a/frontend/src/component/feature/FeatureView/FeatureView.tsx b/frontend/src/component/feature/FeatureView/FeatureView.tsx index 502f3fd414..121db836ab 100644 --- a/frontend/src/component/feature/FeatureView/FeatureView.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureView.tsx @@ -248,7 +248,7 @@ export const FeatureView = () => { }} onClose={() => setShowDelDialog(false)} projectId={projectId} - featureId={featureId} + featureIds={[featureId]} /> { setFeatureArchiveState(undefined); }} - featureId={featureArchiveState || ''} + featureIds={[featureArchiveState || '']} projectId={projectId} />{' '} ); diff --git a/frontend/src/component/project/Project/ProjectFeatureToggles/SelectionActionsBar/ArchiveButton/ArchiveButton.tsx b/frontend/src/component/project/Project/ProjectFeatureToggles/SelectionActionsBar/ArchiveButton/ArchiveButton.tsx new file mode 100644 index 0000000000..e313e084be --- /dev/null +++ b/frontend/src/component/project/Project/ProjectFeatureToggles/SelectionActionsBar/ArchiveButton/ArchiveButton.tsx @@ -0,0 +1,50 @@ +import { useState, VFC } from 'react'; +import { Button } from '@mui/material'; +import { Archive } from '@mui/icons-material'; +import { PermissionHOC } from 'component/common/PermissionHOC/PermissionHOC'; +import { DELETE_FEATURE } from 'component/providers/AccessProvider/permissions'; +import useProject from 'hooks/api/getters/useProject/useProject'; +import { FeatureArchiveDialog } from 'component/common/FeatureArchiveDialog/FeatureArchiveDialog'; + +interface IArchiveButtonProps { + projectId: string; + features: string[]; +} + +export const ArchiveButton: VFC = ({ + projectId, + features, +}) => { + const { refetch } = useProject(projectId); + const [isDialogOpen, setIsDialogOpen] = useState(false); + + const onConfirm = async () => { + setIsDialogOpen(false); + await refetch(); + }; + + return ( + <> + + {({ hasAccess }) => ( + + )} + + setIsDialogOpen(false)} + /> + + ); +}; diff --git a/frontend/src/component/project/Project/ProjectFeatureToggles/SelectionActionsBar/MarkAsStaleButtons/MarkAsStaleButtons.tsx b/frontend/src/component/project/Project/ProjectFeatureToggles/SelectionActionsBar/MarkAsStaleButtons/MarkAsStaleButtons.tsx new file mode 100644 index 0000000000..e00944e458 --- /dev/null +++ b/frontend/src/component/project/Project/ProjectFeatureToggles/SelectionActionsBar/MarkAsStaleButtons/MarkAsStaleButtons.tsx @@ -0,0 +1,54 @@ +import { VFC } from 'react'; +import { Button } from '@mui/material'; +import { WatchLater } from '@mui/icons-material'; +import type { FeatureSchema } from 'openapi'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; +import { UPDATE_FEATURE } from 'component/providers/AccessProvider/permissions'; +import { PermissionHOC } from 'component/common/PermissionHOC/PermissionHOC'; + +interface IMarkAsStaleButtonsProps { + projectId: string; + data: FeatureSchema[]; +} + +export const MarkAsStaleButtons: VFC = ({ + projectId, + data, +}) => { + const hasStale = data.some(d => d.stale); + const hasUnstale = data.some(d => !d.stale); + + return ( + + {({ hasAccess }) => ( + <> + } + variant="outlined" + size="small" + disabled={!hasAccess} + > + Mark as stale + + } + /> + } + variant="outlined" + size="small" + > + Un-mark as stale + + } + /> + + )} + + ); +}; diff --git a/frontend/src/component/project/Project/ProjectFeatureToggles/SelectionActionsBar/SelectionActionsBar.tsx b/frontend/src/component/project/Project/ProjectFeatureToggles/SelectionActionsBar/SelectionActionsBar.tsx index 27525e5868..170519806b 100644 --- a/frontend/src/component/project/Project/ProjectFeatureToggles/SelectionActionsBar/SelectionActionsBar.tsx +++ b/frontend/src/component/project/Project/ProjectFeatureToggles/SelectionActionsBar/SelectionActionsBar.tsx @@ -1,26 +1,30 @@ import { useMemo, useState, VFC } from 'react'; import { Box, Button, Paper, styled, Typography } from '@mui/material'; -import { Archive, FileDownload, Label, WatchLater } from '@mui/icons-material'; +import { FileDownload, Label } from '@mui/icons-material'; import type { FeatureSchema } from 'openapi'; import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; import { ExportDialog } from 'component/feature/FeatureToggleList/ExportDialog'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; +import { ArchiveButton } from './ArchiveButton/ArchiveButton'; +import { MarkAsStaleButtons } from './MarkAsStaleButtons/MarkAsStaleButtons'; interface ISelectionActionsBarProps { selectedIds: string[]; data: FeatureSchema[]; + projectId: string; } const StyledContainer = styled(Box)(() => ({ display: 'flex', justifyContent: 'center', width: '100%', + flexWrap: 'wrap', })); const StyledBar = styled(Paper)(({ theme }) => ({ display: 'flex', alignItems: 'center', - justifyContent: 'space-between', + justifyContent: 'flex-end', marginTop: theme.spacing(2), marginLeft: 'auto', marginRight: 'auto', @@ -28,7 +32,8 @@ const StyledBar = styled(Paper)(({ theme }) => ({ backgroundColor: theme.palette.background.paper, border: `1px solid ${theme.palette.secondary.main}`, borderRadius: theme.shape.borderRadiusLarge, - columnGap: theme.spacing(1), + gap: theme.spacing(1), + flexWrap: 'wrap', })); const StyledCount = styled('span')(({ theme }) => ({ @@ -39,12 +44,14 @@ const StyledCount = styled('span')(({ theme }) => ({ })); const StyledText = styled(Typography)(({ theme }) => ({ - marginRight: theme.spacing(2), + paddingRight: theme.spacing(2), + marginRight: 'auto', })); export const SelectionActionsBar: VFC = ({ selectedIds, data, + projectId, }) => { const { uiConfig } = useUiConfig(); const [showExportDialog, setShowExportDialog] = useState(false); @@ -71,22 +78,8 @@ export const SelectionActionsBar: VFC = ({ {selectedIds.length}  selected - - + +