mirror of
https://github.com/Unleash/unleash.git
synced 2025-03-18 00:19:49 +01:00
feat: Align switches in table actions (#1082)
* feat: strateges state changing switch * refactor: ActionCell for table * fix: remove image clipping for webhook icons * feat: align addons switch in table * feat: align enviromnemnts table switch * fix: disallow turning off protected environment * refactor: move environment table sub-components * feat: add predefined badge to default environment * feat: environment reorder handle hightlight * fix: environment table padding when searching * Update src/hooks/api/actions/useStrategiesApi/useStrategiesApi.ts Co-authored-by: olav <mail@olav.io> * refactor: toggle addon promise * remove dragging highlight * fix: strategy switch tooltip * fix: switch tooltips Co-authored-by: olav <mail@olav.io>
This commit is contained in:
parent
407e3a5f55
commit
51e5939f68
frontend/src
component
addons/AddonList
AddonIcon
AvailableAddons
ConfiguredAddons
common/Table/cells/ActionCell
environments
EnvironmentCard
EnvironmentTable
strategies/StrategiesList
hooks/api/actions/useStrategiesApi
@ -12,7 +12,6 @@ const style: React.CSSProperties = {
|
||||
width: '32.5px',
|
||||
height: '32.5px',
|
||||
marginRight: '16px',
|
||||
borderRadius: '50%',
|
||||
};
|
||||
|
||||
interface IAddonIconProps {
|
||||
|
@ -87,7 +87,6 @@ export const AvailableAddons = ({
|
||||
sortType: 'alphanumeric',
|
||||
},
|
||||
{
|
||||
Header: 'Actions',
|
||||
id: 'Actions',
|
||||
align: 'center',
|
||||
Cell: ({ row: { original } }: any) => (
|
||||
|
@ -52,10 +52,13 @@ export const ConfiguredAddons = () => {
|
||||
setToastData({
|
||||
type: 'success',
|
||||
title: 'Success',
|
||||
text: 'Addon state switched successfully',
|
||||
text: !addon.enabled
|
||||
? 'Addon is now active'
|
||||
: 'Addon is now disabled',
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
setToastApiError(formatUnknownError(error));
|
||||
throw error; // caught by optimistic update
|
||||
}
|
||||
},
|
||||
[setToastApiError, refetchAddons, setToastData, updateAddon]
|
||||
@ -96,12 +99,16 @@ export const ConfiguredAddons = () => {
|
||||
Header: 'Actions',
|
||||
id: 'Actions',
|
||||
align: 'center',
|
||||
Cell: ({ row: { original } }: any) => (
|
||||
Cell: ({
|
||||
row: { original },
|
||||
}: {
|
||||
row: { original: IAddon };
|
||||
}) => (
|
||||
<ConfiguredAddonsActionsCell
|
||||
setShowDelete={setShowDelete}
|
||||
toggleAddon={toggleAddon}
|
||||
setDeletedAddon={setDeletedAddon}
|
||||
original={original as IAddon}
|
||||
original={original}
|
||||
/>
|
||||
),
|
||||
width: 150,
|
||||
|
@ -1,7 +1,9 @@
|
||||
import { Visibility, VisibilityOff, Edit, Delete } from '@mui/icons-material';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { Edit, Delete } from '@mui/icons-material';
|
||||
import { Tooltip } from '@mui/material';
|
||||
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
|
||||
import PermissionSwitch from 'component/common/PermissionSwitch/PermissionSwitch';
|
||||
import { ActionCell } from 'component/common/Table/cells/ActionCell/ActionCell';
|
||||
import { useOptimisticUpdate } from 'component/project/Project/ProjectFeatureToggles/FeatureToggleSwitch/hooks/useOptimisticUpdate';
|
||||
import {
|
||||
UPDATE_ADDON,
|
||||
DELETE_ADDON,
|
||||
@ -23,19 +25,32 @@ export const ConfiguredAddonsActionsCell = ({
|
||||
original,
|
||||
}: IConfiguredAddonsActionsCellProps) => {
|
||||
const navigate = useNavigate();
|
||||
const [isEnabled, setIsEnabled, rollbackIsChecked] =
|
||||
useOptimisticUpdate<boolean>(original.enabled);
|
||||
|
||||
const onClick = () => {
|
||||
setIsEnabled(!isEnabled);
|
||||
toggleAddon(original).catch(rollbackIsChecked);
|
||||
};
|
||||
|
||||
return (
|
||||
<ActionCell>
|
||||
<PermissionIconButton
|
||||
permission={UPDATE_ADDON}
|
||||
onClick={() => toggleAddon(original)}
|
||||
tooltipProps={{ title: 'Toggle addon' }}
|
||||
<Tooltip
|
||||
title={
|
||||
isEnabled
|
||||
? `Disable addon ${original.provider}`
|
||||
: `Enable addon ${original.provider}`
|
||||
}
|
||||
arrow
|
||||
describeChild
|
||||
>
|
||||
<ConditionallyRender
|
||||
condition={original.enabled}
|
||||
show={<Visibility />}
|
||||
elseShow={<VisibilityOff />}
|
||||
<PermissionSwitch
|
||||
permission={UPDATE_ADDON}
|
||||
checked={isEnabled}
|
||||
onClick={onClick}
|
||||
/>
|
||||
</PermissionIconButton>
|
||||
</Tooltip>
|
||||
<ActionCell.Divider />
|
||||
<PermissionIconButton
|
||||
permission={UPDATE_ADDON}
|
||||
tooltipProps={{ title: 'Edit Addon' }}
|
||||
|
@ -0,0 +1,15 @@
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
export const useStyles = makeStyles()(theme => ({
|
||||
container: {
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
alignItems: 'center',
|
||||
padding: theme.spacing(0, 1.5),
|
||||
},
|
||||
divider: {
|
||||
borderColor: theme.palette.dividerAlternative,
|
||||
height: theme.spacing(3),
|
||||
margin: theme.spacing(0, 2),
|
||||
},
|
||||
}));
|
@ -1,14 +1,26 @@
|
||||
import { Box } from '@mui/material';
|
||||
import { ReactNode } from 'react';
|
||||
import { Box, Divider } from '@mui/material';
|
||||
import { FC, VFC } from 'react';
|
||||
import { useStyles } from './ActionCell.styles';
|
||||
|
||||
interface IContextActionsCellProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const ActionCell = ({ children }: IContextActionsCellProps) => {
|
||||
const ActionCellDivider: VFC = () => {
|
||||
const { classes } = useStyles();
|
||||
return (
|
||||
<Box sx={{ display: 'flex', justifyContent: 'flex-end', px: 2 }}>
|
||||
{children}
|
||||
</Box>
|
||||
<Divider
|
||||
className={classes.divider}
|
||||
orientation="vertical"
|
||||
variant="middle"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const ActionCellComponent: FC & {
|
||||
Divider: typeof ActionCellDivider;
|
||||
} = ({ children }) => {
|
||||
const { classes } = useStyles();
|
||||
|
||||
return <Box className={classes.container}>{children}</Box>;
|
||||
};
|
||||
|
||||
ActionCellComponent.Divider = ActionCellDivider;
|
||||
|
||||
export const ActionCell = ActionCellComponent;
|
||||
|
@ -22,7 +22,7 @@ export const useStyles = makeStyles()(theme => ({
|
||||
infoContainer: {
|
||||
marginTop: '1rem',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
justifyContent: 'space-around',
|
||||
},
|
||||
infoInnerContainer: {
|
||||
textAlign: 'center',
|
||||
|
@ -3,12 +3,7 @@ import {
|
||||
DELETE_ENVIRONMENT,
|
||||
UPDATE_ENVIRONMENT,
|
||||
} from 'component/providers/AccessProvider/permissions';
|
||||
import {
|
||||
Edit,
|
||||
Delete,
|
||||
DragIndicator,
|
||||
PowerSettingsNew,
|
||||
} from '@mui/icons-material';
|
||||
import { Edit, Delete } from '@mui/icons-material';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { IconButton, Tooltip } from '@mui/material';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
@ -16,14 +11,14 @@ import AccessContext from 'contexts/AccessContext';
|
||||
import { useContext, useState } from 'react';
|
||||
import { IEnvironment } from 'interfaces/environments';
|
||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||
import EnvironmentToggleConfirm from '../EnvironmentToggleConfirm/EnvironmentToggleConfirm';
|
||||
import EnvironmentDeleteConfirm from '../EnvironmentDeleteConfirm/EnvironmentDeleteConfirm';
|
||||
import EnvironmentToggleConfirm from '../../EnvironmentToggleConfirm/EnvironmentToggleConfirm';
|
||||
import EnvironmentDeleteConfirm from '../../EnvironmentDeleteConfirm/EnvironmentDeleteConfirm';
|
||||
import useEnvironmentApi from 'hooks/api/actions/useEnvironmentApi/useEnvironmentApi';
|
||||
import useProjectRolePermissions from 'hooks/api/getters/useProjectRolePermissions/useProjectRolePermissions';
|
||||
import { useEnvironments } from 'hooks/api/getters/useEnvironments/useEnvironments';
|
||||
import useToast from 'hooks/useToast';
|
||||
import { useId } from 'hooks/useId';
|
||||
import { useSearchHighlightContext } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
|
||||
import PermissionSwitch from 'component/common/PermissionSwitch/PermissionSwitch';
|
||||
|
||||
interface IEnvironmentTableActionsProps {
|
||||
environment: IEnvironment;
|
||||
@ -35,7 +30,6 @@ export const EnvironmentActionCell = ({
|
||||
const navigate = useNavigate();
|
||||
const { hasAccess } = useContext(AccessContext);
|
||||
const updatePermission = hasAccess(UPDATE_ENVIRONMENT);
|
||||
const { searchQuery } = useSearchHighlightContext();
|
||||
|
||||
const { setToastApiError, setToastData } = useToast();
|
||||
const { refetchEnvironments } = useEnvironments();
|
||||
@ -73,8 +67,8 @@ export const EnvironmentActionCell = ({
|
||||
|
||||
const handleToggleEnvironmentOn = async () => {
|
||||
try {
|
||||
await toggleEnvironmentOn(environment.name);
|
||||
setToggleModal(false);
|
||||
await toggleEnvironmentOn(environment.name);
|
||||
setToastData({
|
||||
type: 'success',
|
||||
title: 'Project environment enabled',
|
||||
@ -88,8 +82,8 @@ export const EnvironmentActionCell = ({
|
||||
|
||||
const handleToggleEnvironmentOff = async () => {
|
||||
try {
|
||||
await toggleEnvironmentOff(environment.name);
|
||||
setToggleModal(false);
|
||||
await toggleEnvironmentOff(environment.name);
|
||||
setToastData({
|
||||
type: 'success',
|
||||
title: 'Project environment disabled',
|
||||
@ -102,37 +96,28 @@ export const EnvironmentActionCell = ({
|
||||
};
|
||||
|
||||
const toggleIconTooltip = environment.enabled
|
||||
? 'Disable environment'
|
||||
: 'Enable environment';
|
||||
? `Disable environment ${environment.name}`
|
||||
: `Enable environment ${environment.name}`;
|
||||
|
||||
const editId = useId();
|
||||
const deleteId = useId();
|
||||
|
||||
// Allow drag and drop if the user is permitted to reorder environments.
|
||||
// Disable drag and drop while searching since some rows may be hidden.
|
||||
const enableDragAndDrop = updatePermission && !searchQuery;
|
||||
|
||||
return (
|
||||
<ActionCell>
|
||||
<ConditionallyRender
|
||||
condition={enableDragAndDrop}
|
||||
show={
|
||||
<IconButton size="large">
|
||||
<DragIndicator titleAccess="Drag" cursor="grab" />
|
||||
</IconButton>
|
||||
}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={updatePermission}
|
||||
show={
|
||||
<Tooltip title={toggleIconTooltip} arrow>
|
||||
<IconButton
|
||||
onClick={() => setToggleModal(true)}
|
||||
size="large"
|
||||
>
|
||||
<PowerSettingsNew />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<>
|
||||
<Tooltip title={toggleIconTooltip} arrow describeChild>
|
||||
<PermissionSwitch
|
||||
permission={UPDATE_ENVIRONMENT}
|
||||
checked={environment.enabled}
|
||||
onClick={() => setToggleModal(true)}
|
||||
disabled={environment.protected}
|
||||
/>
|
||||
</Tooltip>
|
||||
<ActionCell.Divider />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
@ -141,7 +126,7 @@ export const EnvironmentActionCell = ({
|
||||
<Tooltip
|
||||
title={
|
||||
environment.protected
|
||||
? 'You cannot edit environment'
|
||||
? 'You cannot edit protected environment'
|
||||
: 'Edit environment'
|
||||
}
|
||||
arrow
|
||||
@ -169,9 +154,10 @@ export const EnvironmentActionCell = ({
|
||||
<Tooltip
|
||||
title={
|
||||
environment.protected
|
||||
? 'You cannot delete environment'
|
||||
? 'You cannot delete protected environment'
|
||||
: 'Delete environment'
|
||||
}
|
||||
describeChild
|
||||
arrow
|
||||
>
|
||||
<span id={deleteId}>
|
@ -0,0 +1,42 @@
|
||||
import { useContext, VFC } from 'react';
|
||||
import { styled } from '@mui/material';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { useSearchHighlightContext } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
|
||||
import { Box, IconButton } from '@mui/material';
|
||||
import { CloudCircle, DragIndicator } from '@mui/icons-material';
|
||||
import { UPDATE_ENVIRONMENT } from 'component/providers/AccessProvider/permissions';
|
||||
import AccessContext from 'contexts/AccessContext';
|
||||
|
||||
const DragIcon = styled(IconButton)(
|
||||
({ theme }) => `
|
||||
padding: ${theme.spacing(0, 1, 0, 0)};
|
||||
cursor: inherit;
|
||||
transition: color 0.2s ease-in-out;
|
||||
`
|
||||
);
|
||||
|
||||
export const EnvironmentIconCell: VFC = () => {
|
||||
const { hasAccess } = useContext(AccessContext);
|
||||
const updatePermission = hasAccess(UPDATE_ENVIRONMENT);
|
||||
const { searchQuery } = useSearchHighlightContext();
|
||||
|
||||
// Allow drag and drop if the user is permitted to reorder environments.
|
||||
// Disable drag and drop while searching since some rows may be hidden.
|
||||
const enableDragAndDrop = updatePermission && !searchQuery;
|
||||
return (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', pl: 2 }}>
|
||||
<ConditionallyRender
|
||||
condition={enableDragAndDrop}
|
||||
show={
|
||||
<DragIcon size="large" disableRipple disabled>
|
||||
<DragIndicator
|
||||
titleAccess="Drag to reorder"
|
||||
cursor="grab"
|
||||
/>
|
||||
</DragIcon>
|
||||
}
|
||||
/>
|
||||
<CloudCircle color="disabled" />
|
||||
</Box>
|
||||
);
|
||||
};
|
@ -21,6 +21,10 @@ export const EnvironmentNameCell = ({
|
||||
condition={!environment.enabled}
|
||||
show={<StatusBadge severity="warning">Disabled</StatusBadge>}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={environment.protected}
|
||||
show={<StatusBadge severity="success">Predefined</StatusBadge>}
|
||||
/>
|
||||
</TextCell>
|
||||
);
|
||||
};
|
@ -11,11 +11,6 @@ import {
|
||||
import { useCallback } from 'react';
|
||||
import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
|
||||
import { Alert, styled, TableBody } from '@mui/material';
|
||||
import { CloudCircle } from '@mui/icons-material';
|
||||
import { IconCell } from 'component/common/Table/cells/IconCell/IconCell';
|
||||
import { EnvironmentActionCell } from 'component/environments/EnvironmentActionCell/EnvironmentActionCell';
|
||||
import { EnvironmentNameCell } from 'component/environments/EnvironmentNameCell/EnvironmentNameCell';
|
||||
import { EnvironmentRow } from 'component/environments/EnvironmentRow/EnvironmentRow';
|
||||
import { MoveListItem } from 'hooks/useDragItem';
|
||||
import useToast from 'hooks/useToast';
|
||||
import useEnvironmentApi, {
|
||||
@ -23,6 +18,10 @@ import useEnvironmentApi, {
|
||||
} from 'hooks/api/actions/useEnvironmentApi/useEnvironmentApi';
|
||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { EnvironmentRow } from './EnvironmentRow/EnvironmentRow';
|
||||
import { EnvironmentNameCell } from './EnvironmentNameCell/EnvironmentNameCell';
|
||||
import { EnvironmentActionCell } from './EnvironmentActionCell/EnvironmentActionCell';
|
||||
import { EnvironmentIconCell } from './EnvironmentIconCell/EnvironmentIconCell';
|
||||
import { Search } from 'component/common/Search/Search';
|
||||
|
||||
const StyledAlert = styled(Alert)(({ theme }) => ({
|
||||
@ -137,7 +136,7 @@ const COLUMNS = [
|
||||
{
|
||||
id: 'Icon',
|
||||
width: '1%',
|
||||
Cell: () => <IconCell icon={<CloudCircle color="disabled" />} />,
|
||||
Cell: () => <EnvironmentIconCell />,
|
||||
disableGlobalFilter: true,
|
||||
},
|
||||
{
|
||||
|
@ -1,17 +1,7 @@
|
||||
import { useState, useMemo } from 'react';
|
||||
import { useState, useMemo, useCallback } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { IconButton, Tooltip, Box } from '@mui/material';
|
||||
import {
|
||||
Delete,
|
||||
Edit,
|
||||
Extension,
|
||||
Visibility,
|
||||
VisibilityOff,
|
||||
} from '@mui/icons-material';
|
||||
import {
|
||||
DELETE_STRATEGY,
|
||||
UPDATE_STRATEGY,
|
||||
} from 'component/providers/AccessProvider/permissions';
|
||||
import { Box } from '@mui/material';
|
||||
import { Extension } from '@mui/icons-material';
|
||||
import {
|
||||
Table,
|
||||
SortableTableHeader,
|
||||
@ -20,11 +10,11 @@ import {
|
||||
TableRow,
|
||||
TablePlaceholder,
|
||||
} from 'component/common/Table';
|
||||
import { ActionCell } from 'component/common/Table/cells/ActionCell/ActionCell';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { PageContent } from 'component/common/PageContent/PageContent';
|
||||
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
||||
import { Dialogue } from 'component/common/Dialogue/Dialogue';
|
||||
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
|
||||
import { formatStrategyName } from 'utils/strategyNames';
|
||||
import { useStrategies } from 'hooks/api/getters/useStrategies/useStrategies';
|
||||
import useStrategiesApi from 'hooks/api/actions/useStrategiesApi/useStrategiesApi';
|
||||
@ -37,6 +27,9 @@ import { sortTypes } from 'utils/sortTypes';
|
||||
import { useTable, useGlobalFilter, useSortBy } from 'react-table';
|
||||
import { AddStrategyButton } from './AddStrategyButton/AddStrategyButton';
|
||||
import { StatusBadge } from 'component/common/StatusBadge/StatusBadge';
|
||||
import { StrategySwitch } from './StrategySwitch/StrategySwitch';
|
||||
import { StrategyEditButton } from './StrategyEditButton/StrategyEditButton';
|
||||
import { StrategyDeleteButton } from './StrategyDeleteButton/StrategyDeleteButton';
|
||||
import { Search } from 'component/common/Search/Search';
|
||||
|
||||
interface IDialogueMetaData {
|
||||
@ -78,6 +71,85 @@ export const StrategiesList = () => {
|
||||
);
|
||||
}, [strategies, loading]);
|
||||
|
||||
const onToggle = useCallback(
|
||||
(strategy: IStrategy) => (deprecated: boolean) => {
|
||||
if (deprecated) {
|
||||
setDialogueMetaData({
|
||||
show: true,
|
||||
title: 'Really reactivate strategy?',
|
||||
onConfirm: async () => {
|
||||
try {
|
||||
await reactivateStrategy(strategy);
|
||||
refetchStrategies();
|
||||
setToastData({
|
||||
type: 'success',
|
||||
title: 'Success',
|
||||
text: 'Strategy reactivated successfully',
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
setToastApiError(formatUnknownError(error));
|
||||
}
|
||||
},
|
||||
});
|
||||
} else {
|
||||
setDialogueMetaData({
|
||||
show: true,
|
||||
title: 'Really deprecate strategy?',
|
||||
onConfirm: async () => {
|
||||
try {
|
||||
await deprecateStrategy(strategy);
|
||||
refetchStrategies();
|
||||
setToastData({
|
||||
type: 'success',
|
||||
title: 'Success',
|
||||
text: 'Strategy deprecated successfully',
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
setToastApiError(formatUnknownError(error));
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
[
|
||||
deprecateStrategy,
|
||||
reactivateStrategy,
|
||||
refetchStrategies,
|
||||
setToastApiError,
|
||||
setToastData,
|
||||
]
|
||||
);
|
||||
|
||||
const onDeleteStrategy = useCallback(
|
||||
(strategy: IStrategy) => {
|
||||
setDialogueMetaData({
|
||||
show: true,
|
||||
title: 'Really delete strategy?',
|
||||
onConfirm: async () => {
|
||||
try {
|
||||
await removeStrategy(strategy);
|
||||
refetchStrategies();
|
||||
setToastData({
|
||||
type: 'success',
|
||||
title: 'Success',
|
||||
text: 'Strategy deleted successfully',
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
setToastApiError(formatUnknownError(error));
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
[removeStrategy, refetchStrategies, setToastApiError, setToastData]
|
||||
);
|
||||
|
||||
const onEditStrategy = useCallback(
|
||||
(strategy: IStrategy) => {
|
||||
navigate(`/strategies/${strategy.name}/edit`);
|
||||
},
|
||||
[navigate]
|
||||
);
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
{
|
||||
@ -86,7 +158,7 @@ export const StrategiesList = () => {
|
||||
<Box
|
||||
data-loading
|
||||
sx={{
|
||||
pl: 2,
|
||||
pl: 3,
|
||||
pr: 1,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
@ -134,18 +206,22 @@ export const StrategiesList = () => {
|
||||
id: 'Actions',
|
||||
align: 'center',
|
||||
Cell: ({ row: { original } }: any) => (
|
||||
<Box
|
||||
sx={{ display: 'flex', justifyContent: 'flex-end' }}
|
||||
data-loading
|
||||
>
|
||||
<ConditionallyRender
|
||||
condition={original.deprecated}
|
||||
show={reactivateButton(original)}
|
||||
elseShow={deprecateButton(original)}
|
||||
<ActionCell>
|
||||
<StrategySwitch
|
||||
deprecated={original.deprecated}
|
||||
onToggle={onToggle(original)}
|
||||
disabled={original.name === 'default'}
|
||||
/>
|
||||
{editButton(original)}
|
||||
{deleteButton(original)}
|
||||
</Box>
|
||||
<ActionCell.Divider />
|
||||
<StrategyEditButton
|
||||
strategy={original}
|
||||
onClick={() => onEditStrategy(original)}
|
||||
/>
|
||||
<StrategyDeleteButton
|
||||
strategy={original}
|
||||
onClick={() => onDeleteStrategy(original)}
|
||||
/>
|
||||
</ActionCell>
|
||||
),
|
||||
width: 150,
|
||||
disableGlobalFilter: true,
|
||||
@ -161,8 +237,7 @@ export const StrategiesList = () => {
|
||||
sortType: 'number',
|
||||
},
|
||||
],
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[]
|
||||
[onToggle, onEditStrategy, onDeleteStrategy]
|
||||
);
|
||||
|
||||
const initialState = useMemo(
|
||||
@ -195,152 +270,6 @@ export const StrategiesList = () => {
|
||||
useSortBy
|
||||
);
|
||||
|
||||
const onReactivateStrategy = (strategy: IStrategy) => {
|
||||
setDialogueMetaData({
|
||||
show: true,
|
||||
title: 'Really reactivate strategy?',
|
||||
onConfirm: async () => {
|
||||
try {
|
||||
await reactivateStrategy(strategy);
|
||||
refetchStrategies();
|
||||
setToastData({
|
||||
type: 'success',
|
||||
title: 'Success',
|
||||
text: 'Strategy reactivated successfully',
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
setToastApiError(formatUnknownError(error));
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const onDeprecateStrategy = (strategy: IStrategy) => {
|
||||
setDialogueMetaData({
|
||||
show: true,
|
||||
title: 'Really deprecate strategy?',
|
||||
onConfirm: async () => {
|
||||
try {
|
||||
await deprecateStrategy(strategy);
|
||||
refetchStrategies();
|
||||
setToastData({
|
||||
type: 'success',
|
||||
title: 'Success',
|
||||
text: 'Strategy deprecated successfully',
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
setToastApiError(formatUnknownError(error));
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const onDeleteStrategy = (strategy: IStrategy) => {
|
||||
setDialogueMetaData({
|
||||
show: true,
|
||||
title: 'Really delete strategy?',
|
||||
onConfirm: async () => {
|
||||
try {
|
||||
await removeStrategy(strategy);
|
||||
refetchStrategies();
|
||||
setToastData({
|
||||
type: 'success',
|
||||
title: 'Success',
|
||||
text: 'Strategy deleted successfully',
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
setToastApiError(formatUnknownError(error));
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const reactivateButton = (strategy: IStrategy) => (
|
||||
<PermissionIconButton
|
||||
onClick={() => onReactivateStrategy(strategy)}
|
||||
permission={UPDATE_STRATEGY}
|
||||
tooltipProps={{ title: 'Reactivate activation strategy' }}
|
||||
>
|
||||
<VisibilityOff />
|
||||
</PermissionIconButton>
|
||||
);
|
||||
|
||||
const deprecateButton = (strategy: IStrategy) => (
|
||||
<ConditionallyRender
|
||||
condition={strategy.name === 'default'}
|
||||
show={
|
||||
<Tooltip title="You cannot deprecate the default strategy">
|
||||
<div>
|
||||
<IconButton disabled size="large">
|
||||
<Visibility titleAccess="Deprecate strategy" />
|
||||
</IconButton>
|
||||
</div>
|
||||
</Tooltip>
|
||||
}
|
||||
elseShow={
|
||||
<div>
|
||||
<PermissionIconButton
|
||||
onClick={() => onDeprecateStrategy(strategy)}
|
||||
permission={UPDATE_STRATEGY}
|
||||
tooltipProps={{ title: 'Deprecate strategy' }}
|
||||
>
|
||||
<Visibility />
|
||||
</PermissionIconButton>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
const editButton = (strategy: IStrategy) => (
|
||||
<ConditionallyRender
|
||||
condition={strategy?.editable}
|
||||
show={
|
||||
<PermissionIconButton
|
||||
onClick={() =>
|
||||
navigate(`/strategies/${strategy?.name}/edit`)
|
||||
}
|
||||
permission={UPDATE_STRATEGY}
|
||||
tooltipProps={{ title: 'Edit strategy' }}
|
||||
>
|
||||
<Edit />
|
||||
</PermissionIconButton>
|
||||
}
|
||||
elseShow={
|
||||
<Tooltip title="You cannot edit a built-in strategy" arrow>
|
||||
<div>
|
||||
<IconButton disabled size="large">
|
||||
<Edit titleAccess="Edit strategy" />
|
||||
</IconButton>
|
||||
</div>
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
const deleteButton = (strategy: IStrategy) => (
|
||||
<ConditionallyRender
|
||||
condition={strategy?.editable}
|
||||
show={
|
||||
<PermissionIconButton
|
||||
onClick={() => onDeleteStrategy(strategy)}
|
||||
permission={DELETE_STRATEGY}
|
||||
tooltipProps={{ title: 'Delete strategy' }}
|
||||
>
|
||||
<Delete />
|
||||
</PermissionIconButton>
|
||||
}
|
||||
elseShow={
|
||||
<Tooltip title="You cannot delete a built-in strategy" arrow>
|
||||
<div>
|
||||
<IconButton disabled size="large">
|
||||
<Delete titleAccess="Delete strategy" />
|
||||
</IconButton>
|
||||
</div>
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
const onDialogConfirm = () => {
|
||||
dialogueMetaData?.onConfirm();
|
||||
setDialogueMetaData((prev: IDialogueMetaData) => ({
|
||||
|
@ -0,0 +1,41 @@
|
||||
import { VFC } from 'react';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
|
||||
import { Delete } from '@mui/icons-material';
|
||||
import { IconButton, Tooltip } from '@mui/material';
|
||||
import { IStrategy } from 'interfaces/strategy';
|
||||
import { DELETE_STRATEGY } from 'component/providers/AccessProvider/permissions';
|
||||
|
||||
interface IStrategyDeleteButtonProps {
|
||||
strategy: IStrategy;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
export const StrategyDeleteButton: VFC<IStrategyDeleteButtonProps> = ({
|
||||
strategy,
|
||||
onClick,
|
||||
}) => {
|
||||
return (
|
||||
<ConditionallyRender
|
||||
condition={strategy?.editable}
|
||||
show={
|
||||
<PermissionIconButton
|
||||
onClick={onClick}
|
||||
permission={DELETE_STRATEGY}
|
||||
tooltipProps={{ title: 'Delete strategy' }}
|
||||
>
|
||||
<Delete />
|
||||
</PermissionIconButton>
|
||||
}
|
||||
elseShow={
|
||||
<Tooltip title="You cannot delete a built-in strategy" arrow>
|
||||
<div>
|
||||
<IconButton disabled size="large">
|
||||
<Delete titleAccess="Delete strategy" />
|
||||
</IconButton>
|
||||
</div>
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
@ -0,0 +1,39 @@
|
||||
import { VFC } from 'react';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
|
||||
import { Edit } from '@mui/icons-material';
|
||||
import { IconButton, Tooltip } from '@mui/material';
|
||||
import { UPDATE_STRATEGY } from 'component/providers/AccessProvider/permissions';
|
||||
import { IStrategy } from 'interfaces/strategy';
|
||||
|
||||
interface IStrategyEditButtonProps {
|
||||
strategy: IStrategy;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
export const StrategyEditButton: VFC<IStrategyEditButtonProps> = ({
|
||||
strategy,
|
||||
onClick,
|
||||
}) => (
|
||||
<ConditionallyRender
|
||||
condition={strategy?.editable}
|
||||
show={
|
||||
<PermissionIconButton
|
||||
onClick={onClick}
|
||||
permission={UPDATE_STRATEGY}
|
||||
tooltipProps={{ title: 'Edit strategy' }}
|
||||
>
|
||||
<Edit />
|
||||
</PermissionIconButton>
|
||||
}
|
||||
elseShow={
|
||||
<Tooltip title="You cannot edit a built-in strategy" arrow>
|
||||
<div>
|
||||
<IconButton disabled size="large">
|
||||
<Edit titleAccess="Edit strategy" />
|
||||
</IconButton>
|
||||
</div>
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
);
|
@ -0,0 +1,43 @@
|
||||
import { VFC } from 'react';
|
||||
import PermissionSwitch from 'component/common/PermissionSwitch/PermissionSwitch';
|
||||
import { UPDATE_STRATEGY } from 'component/providers/AccessProvider/permissions';
|
||||
import { Tooltip } from '@mui/material';
|
||||
import { useId } from 'hooks/useId';
|
||||
|
||||
interface IStrategySwitchProps {
|
||||
deprecated: boolean;
|
||||
onToggle: (state: boolean) => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export const StrategySwitch: VFC<IStrategySwitchProps> = ({
|
||||
deprecated,
|
||||
disabled,
|
||||
onToggle,
|
||||
}) => {
|
||||
const onClick = () => {
|
||||
onToggle(deprecated);
|
||||
};
|
||||
const id = useId();
|
||||
|
||||
const title = deprecated
|
||||
? 'Excluded from strategy list'
|
||||
: 'Included in strategy list';
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
title={disabled ? 'You cannot disable default strategy' : title}
|
||||
describeChild
|
||||
arrow
|
||||
>
|
||||
<div id={id} role="tooltip">
|
||||
<PermissionSwitch
|
||||
checked={!deprecated}
|
||||
permission={UPDATE_STRATEGY}
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
@ -1,83 +1,94 @@
|
||||
import { IStrategyPayload } from 'interfaces/strategy';
|
||||
import { useCallback } from 'react';
|
||||
import useAPI from '../useApi/useApi';
|
||||
|
||||
const URI = 'api/admin/strategies';
|
||||
|
||||
const useStrategiesApi = () => {
|
||||
const { makeRequest, createRequest, errors, loading } = useAPI({
|
||||
propagateErrors: true,
|
||||
});
|
||||
const URI = 'api/admin/strategies';
|
||||
|
||||
const createStrategy = async (strategy: IStrategyPayload) => {
|
||||
const req = createRequest(URI, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(strategy),
|
||||
});
|
||||
const createStrategy = useCallback(
|
||||
async (strategy: IStrategyPayload) => {
|
||||
const req = createRequest(URI, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(strategy),
|
||||
});
|
||||
|
||||
try {
|
||||
const res = await makeRequest(req.caller, req.id);
|
||||
return makeRequest(req.caller, req.id);
|
||||
},
|
||||
[createRequest, makeRequest]
|
||||
);
|
||||
|
||||
return res;
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
const updateStrategy = useCallback(
|
||||
async (strategy: IStrategyPayload) => {
|
||||
const path = `${URI}/${strategy.name}`;
|
||||
const req = createRequest(path, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(strategy),
|
||||
});
|
||||
|
||||
const updateStrategy = async (strategy: IStrategyPayload) => {
|
||||
const path = `${URI}/${strategy.name}`;
|
||||
const req = createRequest(path, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(strategy),
|
||||
});
|
||||
try {
|
||||
const res = await makeRequest(req.caller, req.id);
|
||||
|
||||
try {
|
||||
const res = await makeRequest(req.caller, req.id);
|
||||
return res;
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
},
|
||||
[createRequest, makeRequest]
|
||||
);
|
||||
|
||||
return res;
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
const removeStrategy = useCallback(
|
||||
async (strategy: IStrategyPayload) => {
|
||||
const path = `${URI}/${strategy.name}`;
|
||||
const req = createRequest(path, { method: 'DELETE' });
|
||||
|
||||
const removeStrategy = async (strategy: IStrategyPayload) => {
|
||||
const path = `${URI}/${strategy.name}`;
|
||||
const req = createRequest(path, { method: 'DELETE' });
|
||||
try {
|
||||
const res = await makeRequest(req.caller, req.id);
|
||||
|
||||
try {
|
||||
const res = await makeRequest(req.caller, req.id);
|
||||
return res;
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
},
|
||||
[createRequest, makeRequest]
|
||||
);
|
||||
|
||||
return res;
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
const deprecateStrategy = useCallback(
|
||||
async (strategy: IStrategyPayload) => {
|
||||
const path = `${URI}/${strategy.name}/deprecate`;
|
||||
const req = createRequest(path, {
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
const deprecateStrategy = async (strategy: IStrategyPayload) => {
|
||||
const path = `${URI}/${strategy.name}/deprecate`;
|
||||
const req = createRequest(path, {
|
||||
method: 'POST',
|
||||
});
|
||||
try {
|
||||
const res = await makeRequest(req.caller, req.id);
|
||||
|
||||
try {
|
||||
const res = await makeRequest(req.caller, req.id);
|
||||
return res;
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
},
|
||||
[createRequest, makeRequest]
|
||||
);
|
||||
|
||||
return res;
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
const reactivateStrategy = useCallback(
|
||||
async (strategy: IStrategyPayload) => {
|
||||
const path = `${URI}/${strategy.name}/reactivate`;
|
||||
const req = createRequest(path, { method: 'POST' });
|
||||
|
||||
const reactivateStrategy = async (strategy: IStrategyPayload) => {
|
||||
const path = `${URI}/${strategy.name}/reactivate`;
|
||||
const req = createRequest(path, { method: 'POST' });
|
||||
try {
|
||||
const res = await makeRequest(req.caller, req.id);
|
||||
|
||||
try {
|
||||
const res = await makeRequest(req.caller, req.id);
|
||||
|
||||
return res;
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
return res;
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
},
|
||||
[createRequest, makeRequest]
|
||||
);
|
||||
|
||||
return {
|
||||
createStrategy,
|
||||
|
Loading…
Reference in New Issue
Block a user