diff --git a/frontend/src/component/common/LifecycleFilters/LifecycleFilters.tsx b/frontend/src/component/common/LifecycleFilters/LifecycleFilters.tsx index fd5532582e..c1324a0b8f 100644 --- a/frontend/src/component/common/LifecycleFilters/LifecycleFilters.tsx +++ b/frontend/src/component/common/LifecycleFilters/LifecycleFilters.tsx @@ -37,7 +37,6 @@ interface ILifecycleFiltersBaseProps { const Wrapper = styled(Box)(({ theme }) => ({ display: 'flex', justifyContent: 'space-between', - minHeight: theme.spacing(7), gap: theme.spacing(2), })); diff --git a/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureToggles.tsx b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureToggles.tsx index 0df18899c0..0941b689d4 100644 --- a/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureToggles.tsx +++ b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureToggles.tsx @@ -20,7 +20,7 @@ import { import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled'; import { useFeatureToggleSwitch } from '../ProjectFeatureToggles/FeatureToggleSwitch/useFeatureToggleSwitch.tsx'; import useLoading from 'hooks/useLoading'; -import { ProjectFeatureTogglesHeader } from './ProjectFeatureTogglesHeader/ProjectFeatureTogglesHeader.tsx'; +import { ProjectFeatureTogglesHeader as LegacyProjectFeatureTogglesHeader } from './ProjectFeatureTogglesHeader/LegacyProjectFeatureTogglesHeader.tsx'; import { createColumnHelper, useReactTable } from '@tanstack/react-table'; import { withTableState } from 'utils/withTableState'; import type { FeatureSearchResponseSchema } from 'openapi'; @@ -41,7 +41,7 @@ import { useProjectFeatureSearchActions, } from './useProjectFeatureSearch.ts'; import { AvatarCell } from './AvatarCell.tsx'; -import { styled } from '@mui/material'; +import { styled, useMediaQuery, useTheme } from '@mui/material'; import useProjectOverview from 'hooks/api/getters/useProjectOverview/useProjectOverview'; import { ConnectSdkDialog } from '../../../onboarding/dialog/ConnectSdkDialog.tsx'; import { ProjectOnboarding } from '../../../onboarding/flow/ProjectOnboarding.tsx'; @@ -57,6 +57,9 @@ import { IMPORT_BUTTON } from 'utils/testIds'; import { ProjectCleanupReminder } from './ProjectCleanupReminder/ProjectCleanupReminder.tsx'; import { formatEnvironmentColumnId } from './formatEnvironmentColumnId.ts'; import { ProjectFeaturesColumnsMenu } from './ProjectFeaturesColumnsMenu/ProjectFeaturesColumnsMenu.tsx'; +import { useUiFlag } from 'hooks/useUiFlag.ts'; +import { ProjectFeatureTogglesHeader } from './ProjectFeatureTogglesHeader/ProjectFeatureTogglesHeader.tsx'; +import { ProjectFlagsSearch } from './ProjectFlagsSearch/ProjectFlagsSearch.tsx'; type ProjectFeatureTogglesProps = { environments: string[]; @@ -71,12 +74,27 @@ const Container = styled('div')(({ theme }) => ({ gap: theme.spacing(2), })); -const FilterRow = styled('div')(({ theme }) => ({ +const LegacyFilterRow = styled('div')(({ theme }) => ({ display: 'flex', flexFlow: 'row wrap', justifyContent: 'space-between', })); +const FiltersContainer = styled('div')(({ theme }) => ({ + display: 'flex', + flexDirection: 'column', + gap: theme.spacing(1), + padding: theme.spacing(2, 3, 2), + [theme.breakpoints.down('md')]: { + padding: theme.spacing(2, 2), + }, +})); + +const FilterRow = styled('div')({ + display: 'flex', + alignItems: 'center', +}); + const ButtonGroup = styled('div')(({ theme }) => ({ display: 'flex', gap: theme.spacing(1), @@ -91,6 +109,9 @@ export const ProjectFeatureToggles = ({ const { project } = useProjectOverview(projectId); const [connectSdkOpen, setConnectSdkOpen] = useState(false); const [modalOpen, setModalOpen] = useState(false); + const flagsUiFilterRefactorEnabled = useUiFlag('flagsUiFilterRefactor'); + const theme = useTheme(); + const isSmallScreen = useMediaQuery(theme.breakpoints.down('md')); const { features, @@ -500,25 +521,32 @@ export const ProjectFeatureToggles = ({ ) : null} { - setTableState({ query }); - }} - dataToExport={data} - environmentsToExport={environments} - actions={ - - } - /> + flagsUiFilterRefactorEnabled ? ( + + ) : ( + { + setTableState({ query }); + }} + dataToExport={data} + environmentsToExport={environments} + actions={ + + } + /> + ) } bodyClass='noop' style={{ cursor: 'inherit' }} @@ -528,31 +556,74 @@ export const ProjectFeatureToggles = ({ aria-busy={isPlaceholder} aria-live='polite' > - - - - - + + + {isSmallScreen ? null : ( + { + setTableState({ query }); + }} + isLoading={loading} + /> + )} + + + + + + {isSmallScreen ? ( + { + setTableState({ query }); + }} + isLoading={loading} + /> + ) : null} + + ) : ( + + + setModalOpen(true)} - tooltipProps={{ title: 'Import' }} - data-testid={IMPORT_BUTTON} - data-loading-project - > - - - - + state={filterState} + onChange={setTableState} + total={loading ? undefined : total} + /> + + setModalOpen(true)} + tooltipProps={{ title: 'Import' }} + data-testid={IMPORT_BUTTON} + data-loading-project + > + + + + + )} void; + dataToExport?: Pick[]; + environmentsToExport?: string[]; + actions?: ReactNode; +} + +/** + * @deprecated remove with `flagsUiFilterRefactor` flag + */ +export const ProjectFeatureTogglesHeader: FC< + IProjectFeatureTogglesHeaderProps +> = ({ + isLoading, + totalItems, + searchQuery, + onChangeSearchQuery, + environmentsToExport, + actions, +}) => { + const projectId = useRequiredPathParam('projectId'); + const headerLoadingRef = useLoading(isLoading || false); + const [showTitle, setShowTitle] = useState(true); + const theme = useTheme(); + const isSmallScreen = useMediaQuery(theme.breakpoints.down('md')); + const [showExportDialog, setShowExportDialog] = useState(false); + const { trackEvent } = usePlausibleTracker(); + const projectOverviewRefactorFeedback = false; + const { openFeedback } = useFeedback('newProjectOverview', 'automatic'); + const handleSearch = (query: string) => { + onChangeSearchQuery?.(query); + trackEvent('search-bar', { + props: { + screen: 'project', + length: query.length, + }, + }); + }; + + const createFeedbackContext = () => { + openFeedback({ + title: 'How easy was it to work with the project overview in Unleash?', + positiveLabel: + 'What do you like most about the updated project overview?', + areasForImprovementsLabel: + 'What improvements are needed in the project overview?', + }); + }; + + return ( + + + setShowTitle(false)} + onBlur={() => setShowTitle(true)} + hasFilters + id='projectFeatureFlags' + /> + } + /> + {actions} + + + setShowExportDialog(true)} + sx={(theme) => ({ + marginRight: theme.spacing(2), + })} + > + + + + + setShowExportDialog(false)} + environments={environmentsToExport || []} + /> + } + /> + + {/* FIXME: remove */} + } + onClick={createFeedbackContext} + variant='outlined' + data-loading + > + Provide feedback + + } + /> + + + } + > + + } + /> + + + ); +}; diff --git a/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureTogglesHeader/ProjectFeatureTogglesHeader.tsx b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureTogglesHeader/ProjectFeatureTogglesHeader.tsx index 6513c33aa1..14651bcf0b 100644 --- a/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureTogglesHeader/ProjectFeatureTogglesHeader.tsx +++ b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureTogglesHeader/ProjectFeatureTogglesHeader.tsx @@ -1,136 +1,59 @@ -import { type ReactNode, type FC, useState } from 'react'; -import { - Box, - IconButton, - Tooltip, - useMediaQuery, - useTheme, -} from '@mui/material'; +import { type FC, useState } from 'react'; +import { Box, IconButton, Tooltip } from '@mui/material'; import useLoading from 'hooks/useLoading'; import { PageHeader } from 'component/common/PageHeader/PageHeader'; -import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; -import { Search } from 'component/common/Search/Search'; import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; import { ExportDialog } from 'component/feature/FeatureToggleList/ExportDialog'; -import type { FeatureSchema } from 'openapi'; -import { usePlausibleTracker } from 'hooks/usePlausibleTracker'; import IosShare from '@mui/icons-material/IosShare'; import { FlagCreationButton } from './FlagCreationButton/FlagCreationButton.tsx'; +import { ImportButton } from './ImportButton/ImportButton.tsx'; -interface IProjectFeatureTogglesHeaderProps { +type ProjectFeatureTogglesHeaderProps = { isLoading?: boolean; totalItems?: number; - searchQuery?: string; - onChangeSearchQuery?: (query: string) => void; - dataToExport?: Pick[]; environmentsToExport?: string[]; - actions?: ReactNode; -} +}; export const ProjectFeatureTogglesHeader: FC< - IProjectFeatureTogglesHeaderProps -> = ({ - isLoading, - totalItems, - searchQuery, - onChangeSearchQuery, - environmentsToExport, - actions, -}) => { + ProjectFeatureTogglesHeaderProps +> = ({ isLoading, totalItems, environmentsToExport }) => { const projectId = useRequiredPathParam('projectId'); const headerLoadingRef = useLoading(isLoading || false); - const [showTitle, setShowTitle] = useState(true); - const theme = useTheme(); - const isSmallScreen = useMediaQuery(theme.breakpoints.down('md')); const [showExportDialog, setShowExportDialog] = useState(false); - const { trackEvent } = usePlausibleTracker(); - const handleSearch = (query: string) => { - onChangeSearchQuery?.(query); - trackEvent('search-bar', { - props: { - screen: 'project', - length: query.length, - }, - }); - }; return ( - ({ - padding: `${theme.spacing(2.5)} ${theme.spacing(3.125)}`, - })} - > + - setShowTitle(false)} - onBlur={() => setShowTitle(true)} - hasFilters - id='projectFeatureFlags' - /> - } - /> - {actions} - setShowExportDialog(true)} - sx={(theme) => ({ - marginRight: theme.spacing(2), - })} > + - setShowExportDialog(false)} - environments={environmentsToExport || []} - /> - } - /> - + {!isLoading ? ( + setShowExportDialog(false)} + environments={environmentsToExport || []} + /> + ) : null} + + + } - > - - } - /> - + /> ); }; diff --git a/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeaturesColumnsMenu/ProjectFeaturesColumnsMenu.tsx b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeaturesColumnsMenu/ProjectFeaturesColumnsMenu.tsx index 17e829aa12..c46bbf39b5 100644 --- a/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeaturesColumnsMenu/ProjectFeaturesColumnsMenu.tsx +++ b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeaturesColumnsMenu/ProjectFeaturesColumnsMenu.tsx @@ -1,4 +1,5 @@ import type { FC } from 'react'; +import { Box } from '@mui/material'; import { ColumnsMenu } from '../ColumnsMenu/ColumnsMenu.tsx'; import { formatEnvironmentColumnId } from '../formatEnvironmentColumnId.ts'; @@ -12,47 +13,49 @@ export const ProjectFeaturesColumnsMenu: FC< ProjectFeaturesColumnsMenuProps > = ({ columnVisibility, environments, onToggle }) => { return ( - ({ - header: environment, - id: formatEnvironmentColumnId(environment), - isVisible: - columnVisibility[ - formatEnvironmentColumnId(environment) - ], - })), - ]} - onToggle={onToggle} - /> + ({ marginLeft: theme.spacing(1) })}> + ({ + header: environment, + id: formatEnvironmentColumnId(environment), + isVisible: + columnVisibility[ + formatEnvironmentColumnId(environment) + ], + })), + ]} + onToggle={onToggle} + /> + ); }; diff --git a/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectLifecycleFilters.tsx b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectLifecycleFilters.tsx index f214ae3995..19568e2286 100644 --- a/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectLifecycleFilters.tsx +++ b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectLifecycleFilters.tsx @@ -4,6 +4,7 @@ import type { FilterItemParamHolder } from '../../../filter/Filters/Filters.tsx' import { useProjectStatus } from 'hooks/api/getters/useProjectStatus/useProjectStatus'; import { LifecycleFilters } from 'component/common/LifecycleFilters/LifecycleFilters.tsx'; import { Box, useMediaQuery, useTheme } from '@mui/material'; +import { useUiFlag } from 'hooks/useUiFlag.ts'; type ProjectLifecycleFiltersProps = { projectId: string; @@ -23,6 +24,7 @@ export const ProjectLifecycleFilters: FC = ({ const { data } = useProjectStatus(projectId); const theme = useTheme(); const isSmallScreen = useMediaQuery(theme.breakpoints.down('md')); + const flagsUiFilterRefactorEnabled = useUiFlag('flagsUiFilterRefactor'); const lifecycleSummary = Object.entries( data?.lifecycleSummary || {}, ).reduce( @@ -48,7 +50,13 @@ export const ProjectLifecycleFilters: FC = ({ = ({ state, onChange, @@ -22,6 +28,10 @@ export const ProjectOverviewFilters: FC = ({ const { tags } = useAllTags(); const { flagCreators } = useProjectFlagCreators(project); const [availableFilters, setAvailableFilters] = useState([]); + const flagsUiFilterRefactorEnabled = useUiFlag('flagsUiFilterRefactor'); + const FilterComponent = flagsUiFilterRefactorEnabled + ? StyledFilters + : Filters; useEffect(() => { const tagsOptions = (tags || []).map((tag) => { @@ -124,7 +134,7 @@ export const ProjectOverviewFilters: FC = ({ }, [JSON.stringify(tags), JSON.stringify(flagCreators)]); return ( -