diff --git a/frontend/src/component/addons/AddonList/AddonIcon/AddonIcon.tsx b/frontend/src/component/addons/AddonList/AddonIcon/AddonIcon.tsx index c4a0304e4c..2fb1ccfd5e 100644 --- a/frontend/src/component/addons/AddonList/AddonIcon/AddonIcon.tsx +++ b/frontend/src/component/addons/AddonList/AddonIcon/AddonIcon.tsx @@ -12,7 +12,6 @@ const style: React.CSSProperties = { width: '32.5px', height: '32.5px', marginRight: '16px', - borderRadius: '50%', }; interface IAddonIconProps { diff --git a/frontend/src/component/addons/AddonList/AvailableAddons/AvailableAddons.tsx b/frontend/src/component/addons/AddonList/AvailableAddons/AvailableAddons.tsx index 4c67c01fe0..ed8d5486a4 100644 --- a/frontend/src/component/addons/AddonList/AvailableAddons/AvailableAddons.tsx +++ b/frontend/src/component/addons/AddonList/AvailableAddons/AvailableAddons.tsx @@ -87,7 +87,6 @@ export const AvailableAddons = ({ sortType: 'alphanumeric', }, { - Header: 'Actions', id: 'Actions', align: 'center', Cell: ({ row: { original } }: any) => ( diff --git a/frontend/src/component/addons/AddonList/ConfiguredAddons/ConfiguredAddons.tsx b/frontend/src/component/addons/AddonList/ConfiguredAddons/ConfiguredAddons.tsx index b80b2d2a08..b6490cfffb 100644 --- a/frontend/src/component/addons/AddonList/ConfiguredAddons/ConfiguredAddons.tsx +++ b/frontend/src/component/addons/AddonList/ConfiguredAddons/ConfiguredAddons.tsx @@ -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 }; + }) => ( ), width: 150, diff --git a/frontend/src/component/addons/AddonList/ConfiguredAddons/ConfiguredAddonsActionCell/ConfiguredAddonsActionsCell.tsx b/frontend/src/component/addons/AddonList/ConfiguredAddons/ConfiguredAddonsActionCell/ConfiguredAddonsActionsCell.tsx index 0e12b607ec..f2a9e21136 100644 --- a/frontend/src/component/addons/AddonList/ConfiguredAddons/ConfiguredAddonsActionCell/ConfiguredAddonsActionsCell.tsx +++ b/frontend/src/component/addons/AddonList/ConfiguredAddons/ConfiguredAddonsActionCell/ConfiguredAddonsActionsCell.tsx @@ -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(original.enabled); + + const onClick = () => { + setIsEnabled(!isEnabled); + toggleAddon(original).catch(rollbackIsChecked); + }; + return ( - toggleAddon(original)} - tooltipProps={{ title: 'Toggle addon' }} + - } - elseShow={} + - + + ({ + 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), + }, +})); diff --git a/frontend/src/component/common/Table/cells/ActionCell/ActionCell.tsx b/frontend/src/component/common/Table/cells/ActionCell/ActionCell.tsx index ff89762d43..30637e2c72 100644 --- a/frontend/src/component/common/Table/cells/ActionCell/ActionCell.tsx +++ b/frontend/src/component/common/Table/cells/ActionCell/ActionCell.tsx @@ -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 ( - - {children} - + ); }; + +const ActionCellComponent: FC & { + Divider: typeof ActionCellDivider; +} = ({ children }) => { + const { classes } = useStyles(); + + return {children}; +}; + +ActionCellComponent.Divider = ActionCellDivider; + +export const ActionCell = ActionCellComponent; diff --git a/frontend/src/component/environments/EnvironmentCard/EnvironmentCard.styles.ts b/frontend/src/component/environments/EnvironmentCard/EnvironmentCard.styles.ts index 8d687a8434..eeb57d6092 100644 --- a/frontend/src/component/environments/EnvironmentCard/EnvironmentCard.styles.ts +++ b/frontend/src/component/environments/EnvironmentCard/EnvironmentCard.styles.ts @@ -22,7 +22,7 @@ export const useStyles = makeStyles()(theme => ({ infoContainer: { marginTop: '1rem', display: 'flex', - justifyContent: 'space-between', + justifyContent: 'space-around', }, infoInnerContainer: { textAlign: 'center', diff --git a/frontend/src/component/environments/EnvironmentActionCell/EnvironmentActionCell.tsx b/frontend/src/component/environments/EnvironmentTable/EnvironmentActionCell/EnvironmentActionCell.tsx similarity index 80% rename from frontend/src/component/environments/EnvironmentActionCell/EnvironmentActionCell.tsx rename to frontend/src/component/environments/EnvironmentTable/EnvironmentActionCell/EnvironmentActionCell.tsx index 9ba499b8ad..baf4ca0b9c 100644 --- a/frontend/src/component/environments/EnvironmentActionCell/EnvironmentActionCell.tsx +++ b/frontend/src/component/environments/EnvironmentTable/EnvironmentActionCell/EnvironmentActionCell.tsx @@ -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 ( - - - - } - /> - setToggleModal(true)} - size="large" - > - - - + <> + + setToggleModal(true)} + disabled={environment.protected} + /> + + + } /> diff --git a/frontend/src/component/environments/EnvironmentTable/EnvironmentIconCell/EnvironmentIconCell.tsx b/frontend/src/component/environments/EnvironmentTable/EnvironmentIconCell/EnvironmentIconCell.tsx new file mode 100644 index 0000000000..66c3b2dd8a --- /dev/null +++ b/frontend/src/component/environments/EnvironmentTable/EnvironmentIconCell/EnvironmentIconCell.tsx @@ -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 ( + + + + + } + /> + + + ); +}; diff --git a/frontend/src/component/environments/EnvironmentNameCell/EnvironmentNameCell.tsx b/frontend/src/component/environments/EnvironmentTable/EnvironmentNameCell/EnvironmentNameCell.tsx similarity index 85% rename from frontend/src/component/environments/EnvironmentNameCell/EnvironmentNameCell.tsx rename to frontend/src/component/environments/EnvironmentTable/EnvironmentNameCell/EnvironmentNameCell.tsx index b13f44e363..6312722f8d 100644 --- a/frontend/src/component/environments/EnvironmentNameCell/EnvironmentNameCell.tsx +++ b/frontend/src/component/environments/EnvironmentTable/EnvironmentNameCell/EnvironmentNameCell.tsx @@ -21,6 +21,10 @@ export const EnvironmentNameCell = ({ condition={!environment.enabled} show={Disabled} /> + Predefined} + /> ); }; diff --git a/frontend/src/component/environments/EnvironmentRow/EnvironmentRow.tsx b/frontend/src/component/environments/EnvironmentTable/EnvironmentRow/EnvironmentRow.tsx similarity index 100% rename from frontend/src/component/environments/EnvironmentRow/EnvironmentRow.tsx rename to frontend/src/component/environments/EnvironmentTable/EnvironmentRow/EnvironmentRow.tsx diff --git a/frontend/src/component/environments/EnvironmentTable/EnvironmentTable.tsx b/frontend/src/component/environments/EnvironmentTable/EnvironmentTable.tsx index e58eb81189..b9d8dde01c 100644 --- a/frontend/src/component/environments/EnvironmentTable/EnvironmentTable.tsx +++ b/frontend/src/component/environments/EnvironmentTable/EnvironmentTable.tsx @@ -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: () => } />, + Cell: () => , disableGlobalFilter: true, }, { diff --git a/frontend/src/component/strategies/StrategiesList/StrategiesList.tsx b/frontend/src/component/strategies/StrategiesList/StrategiesList.tsx index cad1b6eeeb..a7988a1c23 100644 --- a/frontend/src/component/strategies/StrategiesList/StrategiesList.tsx +++ b/frontend/src/component/strategies/StrategiesList/StrategiesList.tsx @@ -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 = () => { { id: 'Actions', align: 'center', Cell: ({ row: { original } }: any) => ( - - + - {editButton(original)} - {deleteButton(original)} - + + onEditStrategy(original)} + /> + onDeleteStrategy(original)} + /> + ), 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) => ( - onReactivateStrategy(strategy)} - permission={UPDATE_STRATEGY} - tooltipProps={{ title: 'Reactivate activation strategy' }} - > - - - ); - - const deprecateButton = (strategy: IStrategy) => ( - -
- - - -
- - } - elseShow={ -
- onDeprecateStrategy(strategy)} - permission={UPDATE_STRATEGY} - tooltipProps={{ title: 'Deprecate strategy' }} - > - - -
- } - /> - ); - - const editButton = (strategy: IStrategy) => ( - - navigate(`/strategies/${strategy?.name}/edit`) - } - permission={UPDATE_STRATEGY} - tooltipProps={{ title: 'Edit strategy' }} - > - -
- } - elseShow={ - -
- - - -
-
- } - /> - ); - - const deleteButton = (strategy: IStrategy) => ( - onDeleteStrategy(strategy)} - permission={DELETE_STRATEGY} - tooltipProps={{ title: 'Delete strategy' }} - > - - - } - elseShow={ - -
- - - -
-
- } - /> - ); - const onDialogConfirm = () => { dialogueMetaData?.onConfirm(); setDialogueMetaData((prev: IDialogueMetaData) => ({ diff --git a/frontend/src/component/strategies/StrategiesList/StrategyDeleteButton/StrategyDeleteButton.tsx b/frontend/src/component/strategies/StrategiesList/StrategyDeleteButton/StrategyDeleteButton.tsx new file mode 100644 index 0000000000..6e7ffd092d --- /dev/null +++ b/frontend/src/component/strategies/StrategiesList/StrategyDeleteButton/StrategyDeleteButton.tsx @@ -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 = ({ + strategy, + onClick, +}) => { + return ( + + + + } + elseShow={ + +
+ + + +
+
+ } + /> + ); +}; diff --git a/frontend/src/component/strategies/StrategiesList/StrategyEditButton/StrategyEditButton.tsx b/frontend/src/component/strategies/StrategiesList/StrategyEditButton/StrategyEditButton.tsx new file mode 100644 index 0000000000..a6cad7129c --- /dev/null +++ b/frontend/src/component/strategies/StrategiesList/StrategyEditButton/StrategyEditButton.tsx @@ -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 = ({ + strategy, + onClick, +}) => ( + + + + } + elseShow={ + +
+ + + +
+
+ } + /> +); diff --git a/frontend/src/component/strategies/StrategiesList/StrategySwitch/StrategySwitch.tsx b/frontend/src/component/strategies/StrategiesList/StrategySwitch/StrategySwitch.tsx new file mode 100644 index 0000000000..21ef30bba9 --- /dev/null +++ b/frontend/src/component/strategies/StrategiesList/StrategySwitch/StrategySwitch.tsx @@ -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 = ({ + deprecated, + disabled, + onToggle, +}) => { + const onClick = () => { + onToggle(deprecated); + }; + const id = useId(); + + const title = deprecated + ? 'Excluded from strategy list' + : 'Included in strategy list'; + + return ( + + + + ); +}; diff --git a/frontend/src/hooks/api/actions/useStrategiesApi/useStrategiesApi.ts b/frontend/src/hooks/api/actions/useStrategiesApi/useStrategiesApi.ts index 3e406798a2..e2c1cc052b 100644 --- a/frontend/src/hooks/api/actions/useStrategiesApi/useStrategiesApi.ts +++ b/frontend/src/hooks/api/actions/useStrategiesApi/useStrategiesApi.ts @@ -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,