diff --git a/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx b/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx index b0f94005a0..3fc0127300 100644 --- a/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx +++ b/frontend/src/component/archive/ArchiveTable/ArchiveTable.tsx @@ -1,13 +1,6 @@ import { PageContent } from 'component/common/PageContent/PageContent'; import { PageHeader } from 'component/common/PageHeader/PageHeader'; -import { - SortableTableHeader, - Table, - TableBody, - TableCell, - TablePlaceholder, - TableRow, -} from 'component/common/Table'; +import { TablePlaceholder, VirtualizedTable } from 'component/common/Table'; import { SortingRule, useFlexLayout, useSortBy, useTable } from 'react-table'; import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext'; import { useMediaQuery } from '@mui/material'; @@ -22,7 +15,6 @@ import { FeatureSeenCell } from 'component/common/Table/cells/FeatureSeenCell/Fe import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell'; import { FeatureStaleCell } from 'component/feature/FeatureToggleList/FeatureStaleCell/FeatureStaleCell'; import { ReviveArchivedFeatureCell } from 'component/archive/ArchiveTable/ReviveArchivedFeatureCell/ReviveArchivedFeatureCell'; -import { useStyles } from 'component/feature/FeatureToggleList/styles'; import { featuresPlaceholder } from 'component/feature/FeatureToggleList/FeatureToggleListTable'; import theme from 'themes/theme'; import { FeatureSchema } from 'openapi'; @@ -31,7 +23,6 @@ import useToast from 'hooks/useToast'; import { formatUnknownError } from 'utils/formatUnknownError'; import { useSearch } from 'hooks/useSearch'; import { FeatureArchivedCell } from './FeatureArchivedCell/FeatureArchivedCell'; -import { useVirtualizedRange } from 'hooks/useVirtualizedRange'; import { useSearchParams } from 'react-router-dom'; export interface IFeaturesArchiveTableProps { @@ -57,8 +48,6 @@ export const ArchiveTable = ({ title, projectId, }: IFeaturesArchiveTableProps) => { - const rowHeight = theme.shape.tableRowHeight; - const { classes } = useStyles(); const isSmallScreen = useMediaQuery(theme.breakpoints.down('md')); const isMediumScreen = useMediaQuery(theme.breakpoints.down('lg')); const { setToastData, setToastApiError } = useToast(); @@ -107,7 +96,7 @@ export const ArchiveTable = ({ align: 'center', }, { - Header: 'Feature toggle name', + Header: 'Name', accessor: 'name', searchable: true, minWidth: 100, @@ -209,8 +198,6 @@ export const ArchiveTable = ({ headerGroups, rows, state: { sortBy }, - getTableBodyProps, - getTableProps, prepareRow, setHiddenColumns, } = useTable( @@ -256,15 +243,12 @@ export const ArchiveTable = ({ setStoredParams({ id: sortBy[0].id, desc: sortBy[0].desc || false }); }, [loading, sortBy, searchValue]); // eslint-disable-line react-hooks/exhaustive-deps - const [firstRenderedIndex, lastRenderedIndex] = - useVirtualizedRange(rowHeight); - return ( - - - - {rows.map((row, index) => { - const isVirtual = - index < firstRenderedIndex || - index > lastRenderedIndex; - - if (isVirtual) { - return null; - } - - prepareRow(row); - return ( - - {row.cells.map(cell => ( - - {cell.render('Cell')} - - ))} - - ); - })} - -
+
({ })); interface IPageHeaderProps { - title: string; + title?: string; titleElement?: ReactNode; subtitle?: string; variant?: TypographyProps['variant']; diff --git a/frontend/src/component/common/Table/VirtualizedTable/VirtualizedTable.styles.ts b/frontend/src/component/common/Table/VirtualizedTable/VirtualizedTable.styles.ts new file mode 100644 index 0000000000..d368e0efaa --- /dev/null +++ b/frontend/src/component/common/Table/VirtualizedTable/VirtualizedTable.styles.ts @@ -0,0 +1,16 @@ +import { makeStyles } from 'tss-react/mui'; + +export const useStyles = makeStyles()(() => ({ + row: { + position: 'absolute', + width: '100%', + }, + cell: { + alignItems: 'center', + display: 'flex', + flexShrink: 0, + '& > *': { + flexGrow: 1, + }, + }, +})); diff --git a/frontend/src/component/common/Table/VirtualizedTable/VirtualizedTable.tsx b/frontend/src/component/common/Table/VirtualizedTable/VirtualizedTable.tsx new file mode 100644 index 0000000000..1fb82e2c88 --- /dev/null +++ b/frontend/src/component/common/Table/VirtualizedTable/VirtualizedTable.tsx @@ -0,0 +1,101 @@ +import { useMemo, VFC } from 'react'; +import { useTheme } from '@mui/material'; +import { + SortableTableHeader, + Table, + TableCell, + TableBody, + TableRow, +} from 'component/common/Table'; +import { useVirtualizedRange } from 'hooks/useVirtualizedRange'; +import { useStyles } from './VirtualizedTable.styles'; +import { HeaderGroup, Row } from 'react-table'; + +interface IVirtualizedTableProps { + rowHeight?: number; + headerGroups: HeaderGroup[]; + rows: Row[]; + prepareRow: (row: Row) => void; +} + +/** + * READ BEFORE USE + * + * Virtualized tables require some setup. + * With this component all but one columns are fixed width, and one fills remaining space. + * Add `maxWidth` to columns that will be static in width, and `minWidth` to the one that should grow. + * + * Remember to add `useFlexLayout` to `useTable` + * (more at: https://react-table-v7.tanstack.com/docs/api/useFlexLayout) + */ +export const VirtualizedTable: VFC = ({ + rowHeight: rowHeightOverride, + headerGroups, + rows, + prepareRow, +}) => { + const { classes } = useStyles(); + const theme = useTheme(); + const rowHeight = useMemo( + () => rowHeightOverride || theme.shape.tableRowHeight, + [rowHeightOverride, theme.shape.tableRowHeight] + ); + + const [firstRenderedIndex, lastRenderedIndex] = + useVirtualizedRange(rowHeight); + + const tableHeight = useMemo( + () => rowHeight * rows.length + theme.shape.tableRowHeightCompact, + [rowHeight, rows.length, theme.shape.tableRowHeightCompact] + ); + + return ( + + + + {rows.map((row, index) => { + const top = + index * rowHeight + theme.shape.tableRowHeightCompact; + + const isVirtual = + index < firstRenderedIndex || index > lastRenderedIndex; + + if (isVirtual) { + return null; + } + + prepareRow(row); + + return ( + + {row.cells.map(cell => ( + + {cell.render('Cell')} + + ))} + + ); + })} + +
+ ); +}; diff --git a/frontend/src/component/common/Table/cells/ActionCell/ActionCell.styles.ts b/frontend/src/component/common/Table/cells/ActionCell/ActionCell.styles.ts index 13595741fa..5f00808831 100644 --- a/frontend/src/component/common/Table/cells/ActionCell/ActionCell.styles.ts +++ b/frontend/src/component/common/Table/cells/ActionCell/ActionCell.styles.ts @@ -3,7 +3,7 @@ import { makeStyles } from 'tss-react/mui'; export const useStyles = makeStyles()(theme => ({ container: { display: 'flex', - justifyContent: 'flex-end', + justifyContent: 'center', alignItems: 'center', padding: theme.spacing(0, 1.5), }, diff --git a/frontend/src/component/common/Table/cells/FeatureNameCell/FeatureNameCell.tsx b/frontend/src/component/common/Table/cells/FeatureNameCell/FeatureNameCell.tsx index e41f34ec41..13f0128a17 100644 --- a/frontend/src/component/common/Table/cells/FeatureNameCell/FeatureNameCell.tsx +++ b/frontend/src/component/common/Table/cells/FeatureNameCell/FeatureNameCell.tsx @@ -11,12 +11,10 @@ interface IFeatureNameCellProps { }; } -export const FeatureNameCell: VFC = ({ row }) => { - return ( - - ); -}; +export const FeatureNameCell: VFC = ({ row }) => ( + +); diff --git a/frontend/src/component/common/Table/index.ts b/frontend/src/component/common/Table/index.ts index fc5e516ad4..3bab70111a 100644 --- a/frontend/src/component/common/Table/index.ts +++ b/frontend/src/component/common/Table/index.ts @@ -3,3 +3,4 @@ export { TableBody, TableRow } from '@mui/material'; export { Table } from './Table/Table'; export { TableCell } from './TableCell/TableCell'; export { TablePlaceholder } from './TablePlaceholder/TablePlaceholder'; +export { VirtualizedTable } from './VirtualizedTable/VirtualizedTable'; diff --git a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListTable.tsx b/frontend/src/component/feature/FeatureToggleList/FeatureToggleListTable.tsx index 177a3e3e6a..fb42b64590 100644 --- a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListTable.tsx +++ b/frontend/src/component/feature/FeatureToggleList/FeatureToggleListTable.tsx @@ -2,14 +2,7 @@ import { useEffect, useMemo, useState, VFC } from 'react'; import { Link, useMediaQuery, useTheme } from '@mui/material'; import { Link as RouterLink, useSearchParams } from 'react-router-dom'; import { SortingRule, useFlexLayout, useSortBy, useTable } from 'react-table'; -import { - Table, - SortableTableHeader, - TableBody, - TableCell, - TableRow, - TablePlaceholder, -} from 'component/common/Table'; +import { TablePlaceholder, VirtualizedTable } from 'component/common/Table'; import { useFeatures } from 'hooks/api/getters/useFeatures/useFeatures'; import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext'; import { DateCell } from 'component/common/Table/cells/DateCell/DateCell'; @@ -22,11 +15,9 @@ import { PageContent } from 'component/common/PageContent/PageContent'; import { PageHeader } from 'component/common/PageHeader/PageHeader'; import { sortTypes } from 'utils/sortTypes'; import { createLocalStorage } from 'utils/createLocalStorage'; -import { useVirtualizedRange } from 'hooks/useVirtualizedRange'; import { FeatureSchema } from 'openapi'; import { CreateFeatureButton } from '../CreateFeatureButton/CreateFeatureButton'; import { FeatureStaleCell } from './FeatureStaleCell/FeatureStaleCell'; -import { useStyles } from './styles'; import { useSearch } from 'hooks/useSearch'; import { Search } from 'component/common/Search/Search'; @@ -108,8 +99,6 @@ const { value: storedParams, setValue: setStoredParams } = createLocalStorage( export const FeatureToggleListTable: VFC = () => { const theme = useTheme(); - const rowHeight = theme.shape.tableRowHeight; - const { classes } = useStyles(); const isSmallScreen = useMediaQuery(theme.breakpoints.down('md')); const isMediumScreen = useMediaQuery(theme.breakpoints.down('lg')); const { features = [], loading } = useFeatures(); @@ -143,8 +132,6 @@ export const FeatureToggleListTable: VFC = () => { ); const { - getTableProps, - getTableBodyProps, headerGroups, rows, prepareRow, @@ -191,12 +178,6 @@ export const FeatureToggleListTable: VFC = () => { setStoredParams({ id: sortBy[0].id, desc: sortBy[0].desc || false }); }, [sortBy, searchValue, setSearchParams]); - const [firstRenderedIndex, lastRenderedIndex] = - useVirtualizedRange(rowHeight); - - const tableHeight = - rowHeight * rows.length + theme.shape.tableRowHeightCompact; - return ( { } > - - - - {rows.map((row, index) => { - const top = - index * rowHeight + - theme.shape.tableRowHeightCompact; - - const isVirtual = - index < firstRenderedIndex || - index > lastRenderedIndex; - - if (isVirtual) { - return null; - } - - prepareRow(row); - return ( - - {row.cells.map(cell => ( - - {cell.render('Cell')} - - ))} - - ); - })} - -
+
({ - actionsContainer: { - display: 'flex', - alignItems: 'center', - }, - listParagraph: { - textAlign: 'center', - }, - searchBarContainer: { - marginBottom: '2rem', - display: 'flex', - gap: '1rem', - justifyContent: 'space-between', - alignItems: 'center', - [theme.breakpoints.down('sm')]: { - display: 'block', - }, - '&.dense': { - marginBottom: '1rem', - }, - }, - searchBar: { - minWidth: '450px', - [theme.breakpoints.down('sm')]: { - minWidth: '100%', - }, - }, - emptyStateListItem: { - border: `2px dashed ${theme.palette.grey[100]}`, - padding: '0.8rem', - textAlign: 'center', - display: 'flex', - justifyContent: 'space-between', - alignItems: 'center', - }, - row: { - position: 'absolute', - width: '100%', - }, - cell: { - alignItems: 'center', - display: 'flex', - flexShrink: 0, - '& > *': { - flexGrow: 1, - }, - }, -})); diff --git a/frontend/src/component/project/Project/Project.tsx b/frontend/src/component/project/Project/Project.tsx index b29e033c5a..a53574c448 100644 --- a/frontend/src/component/project/Project/Project.tsx +++ b/frontend/src/component/project/Project/Project.tsx @@ -33,34 +33,55 @@ const Project = () => { const { isOss } = useUiConfig(); const basePath = `/projects/${projectId}`; + const projectName = project?.name || projectId; const tabData = [ { title: 'Overview', - component: , + component: ( + + ), path: basePath, name: 'overview', }, { title: 'Health', - component: , + component: ( + + ), path: `${basePath}/health`, name: 'health', }, { title: 'Access', - component: , + component: , path: `${basePath}/access`, name: 'access', }, { title: 'Environments', - component: , + component: ( + + ), path: `${basePath}/environments`, name: 'environments', }, { title: 'Archive', - component: , + component: ( + + ), path: `${basePath}/archive`, name: 'archive', }, @@ -116,7 +137,7 @@ const Project = () => {

- {project?.name || projectId} + {projectName}
(() => { + const data = useMemo(() => { if (loading) { return Array(6).fill({ type: '-', @@ -291,7 +282,7 @@ export const ProjectFeatureToggles = ({ environments: { production: { name: 'production', enabled: false }, }, - }) as ListItemType[]; + }) as object[]; } return searchedData; }, [loading, searchedData]); @@ -343,8 +334,6 @@ export const ProjectFeatureToggles = ({ headerGroups, rows, state: { sortBy, hiddenColumns }, - getTableBodyProps, - getTableProps, prepareRow, setHiddenColumns, } = useTable( @@ -392,12 +381,6 @@ export const ProjectFeatureToggles = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [loading, sortBy, hiddenColumns, searchValue, setSearchParams]); - const [firstRenderedIndex, lastRenderedIndex] = - useVirtualizedRange(rowHeight); - - const tableHeight = - rowHeight * rows.length + theme.shape.tableRowHeightCompact; - return ( - - - - {rows.map((row, index) => { - const top = - index * rowHeight + - theme.shape.tableRowHeightCompact; - - const isVirtual = - index < firstRenderedIndex || - index > lastRenderedIndex; - - if (isVirtual) { - return null; - } - - prepareRow(row); - return ( - - {row.cells.map(cell => ( - - {cell.render('Cell')} - - ))} - - ); - })} - -
+
{ - usePageTitle('Project Archived Features'); + usePageTitle(`Project archive – ${projectName}`); return ; }; diff --git a/frontend/src/component/project/Project/ProjectHealth/ProjectHealth.tsx b/frontend/src/component/project/Project/ProjectHealth/ProjectHealth.tsx index 8b7c6a3f92..3eab35fb4e 100644 --- a/frontend/src/component/project/Project/ProjectHealth/ProjectHealth.tsx +++ b/frontend/src/component/project/Project/ProjectHealth/ProjectHealth.tsx @@ -1,19 +1,22 @@ import { useHealthReport } from 'hooks/api/getters/useHealthReport/useHealthReport'; import ApiError from 'component/common/ApiError/ApiError'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; -import { ReportCard } from 'component/Reporting/ReportCard/ReportCard'; import { usePageTitle } from 'hooks/usePageTitle'; -import { ReportTable } from 'component/Reporting/ReportTable/ReportTable'; +import { ReportCard } from './ReportTable/ReportCard/ReportCard'; +import { ReportTable } from './ReportTable/ReportTable'; interface IProjectHealthProps { projectId: string; + projectName: string; } -const ProjectHealth = ({ projectId }: IProjectHealthProps) => { - usePageTitle('Project health'); +const ProjectHealth = ({ projectId, projectName }: IProjectHealthProps) => { + usePageTitle(`Project health – ${projectName}`); - const { healthReport, refetchHealthReport, error } = - useHealthReport(projectId); + const { healthReport, refetchHealthReport, error } = useHealthReport( + projectId, + { refreshInterval: 15 * 1000 } + ); if (!healthReport) { return null; diff --git a/frontend/src/component/Reporting/ReportCard/ReportCard.tsx b/frontend/src/component/project/Project/ProjectHealth/ReportTable/ReportCard/ReportCard.tsx similarity index 100% rename from frontend/src/component/Reporting/ReportCard/ReportCard.tsx rename to frontend/src/component/project/Project/ProjectHealth/ReportTable/ReportCard/ReportCard.tsx diff --git a/frontend/src/component/Reporting/ReportExpiredCell/ReportExpiredCell.tsx b/frontend/src/component/project/Project/ProjectHealth/ReportTable/ReportExpiredCell/ReportExpiredCell.tsx similarity index 55% rename from frontend/src/component/Reporting/ReportExpiredCell/ReportExpiredCell.tsx rename to frontend/src/component/project/Project/ProjectHealth/ReportTable/ReportExpiredCell/ReportExpiredCell.tsx index 960e1d9704..54ec3875c8 100644 --- a/frontend/src/component/Reporting/ReportExpiredCell/ReportExpiredCell.tsx +++ b/frontend/src/component/project/Project/ProjectHealth/ReportTable/ReportExpiredCell/ReportExpiredCell.tsx @@ -1,6 +1,7 @@ import { VFC } from 'react'; +import { Typography, useTheme } from '@mui/material'; import { DateCell } from 'component/common/Table/cells/DateCell/DateCell'; -import { IReportTableRow } from 'component/Reporting/ReportTable/ReportTable'; +import { IReportTableRow } from 'component/project/Project/ProjectHealth/ReportTable/ReportTable'; import { TextCell } from 'component/common/Table/cells/TextCell/TextCell'; interface IReportExpiredCellProps { @@ -10,9 +11,17 @@ interface IReportExpiredCellProps { } export const ReportExpiredCell: VFC = ({ row }) => { + const theme = useTheme(); + if (row.original.expiredAt) { return ; } - return N/A; + return ( + + + N/A + + + ); }; diff --git a/frontend/src/component/Reporting/ReportExpiredCell/formatExpiredAt.ts b/frontend/src/component/project/Project/ProjectHealth/ReportTable/ReportExpiredCell/formatExpiredAt.ts similarity index 86% rename from frontend/src/component/Reporting/ReportExpiredCell/formatExpiredAt.ts rename to frontend/src/component/project/Project/ProjectHealth/ReportTable/ReportExpiredCell/formatExpiredAt.ts index 69df97efef..82c392a34d 100644 --- a/frontend/src/component/Reporting/ReportExpiredCell/formatExpiredAt.ts +++ b/frontend/src/component/project/Project/ProjectHealth/ReportTable/ReportExpiredCell/formatExpiredAt.ts @@ -1,10 +1,6 @@ import { IFeatureToggleListItem } from 'interfaces/featureToggle'; import { PERMISSION, KILLSWITCH } from 'constants/featureToggleTypes'; -import { - getDiffInDays, - expired, - toggleExpiryByTypeMap, -} from 'component/Reporting/utils'; +import { getDiffInDays, expired, toggleExpiryByTypeMap } from '../utils'; import { subDays, parseISO } from 'date-fns'; export const formatExpiredAt = ( diff --git a/frontend/src/component/Reporting/ReportStatusCell/ReportStatusCell.tsx b/frontend/src/component/project/Project/ProjectHealth/ReportTable/ReportStatusCell/ReportStatusCell.tsx similarity index 93% rename from frontend/src/component/Reporting/ReportStatusCell/ReportStatusCell.tsx rename to frontend/src/component/project/Project/ProjectHealth/ReportTable/ReportStatusCell/ReportStatusCell.tsx index 9dc9f57b8b..81b9379c54 100644 --- a/frontend/src/component/Reporting/ReportStatusCell/ReportStatusCell.tsx +++ b/frontend/src/component/project/Project/ProjectHealth/ReportTable/ReportStatusCell/ReportStatusCell.tsx @@ -2,7 +2,7 @@ import { VFC, ReactElement } from 'react'; import { TextCell } from 'component/common/Table/cells/TextCell/TextCell'; import { ReportProblemOutlined, Check } from '@mui/icons-material'; import { styled } from '@mui/material'; -import { IReportTableRow } from 'component/Reporting/ReportTable/ReportTable'; +import { IReportTableRow } from 'component/project/Project/ProjectHealth/ReportTable/ReportTable'; const StyledTextPotentiallyStale = styled('span')(({ theme }) => ({ display: 'flex', diff --git a/frontend/src/component/Reporting/ReportStatusCell/formatStatus.ts b/frontend/src/component/project/Project/ProjectHealth/ReportTable/ReportStatusCell/formatStatus.ts similarity index 90% rename from frontend/src/component/Reporting/ReportStatusCell/formatStatus.ts rename to frontend/src/component/project/Project/ProjectHealth/ReportTable/ReportStatusCell/formatStatus.ts index 419c6f1729..a3775c3b3d 100644 --- a/frontend/src/component/Reporting/ReportStatusCell/formatStatus.ts +++ b/frontend/src/component/project/Project/ProjectHealth/ReportTable/ReportStatusCell/formatStatus.ts @@ -1,5 +1,5 @@ import { IFeatureToggleListItem } from 'interfaces/featureToggle'; -import { getDiffInDays, expired } from 'component/Reporting/utils'; +import { getDiffInDays, expired } from '../utils'; import { PERMISSION, KILLSWITCH } from 'constants/featureToggleTypes'; import { parseISO } from 'date-fns'; diff --git a/frontend/src/component/Reporting/ReportTable/ReportTable.tsx b/frontend/src/component/project/Project/ProjectHealth/ReportTable/ReportTable.tsx similarity index 63% rename from frontend/src/component/Reporting/ReportTable/ReportTable.tsx rename to frontend/src/component/project/Project/ProjectHealth/ReportTable/ReportTable.tsx index a64756cab9..063cea89d1 100644 --- a/frontend/src/component/Reporting/ReportTable/ReportTable.tsx +++ b/frontend/src/component/project/Project/ProjectHealth/ReportTable/ReportTable.tsx @@ -1,31 +1,36 @@ +import { useMemo, useEffect } from 'react'; import { IFeatureToggleListItem } from 'interfaces/featureToggle'; import { SortableTableHeader, + Table, + TableBody, TableCell, TablePlaceholder, + TableRow, + VirtualizedTable, } from 'component/common/Table'; import { PageContent } from 'component/common/PageContent/PageContent'; import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext'; import { PageHeader } from 'component/common/PageHeader/PageHeader'; import { sortTypes } from 'utils/sortTypes'; -import { useSortBy, useGlobalFilter, useTable } from 'react-table'; -import { Table, TableBody, TableRow, useMediaQuery } from '@mui/material'; +import { + useSortBy, + useGlobalFilter, + useTable, + useFlexLayout, +} from 'react-table'; +import { useMediaQuery, useTheme } from '@mui/material'; import { FeatureSeenCell } from 'component/common/Table/cells/FeatureSeenCell/FeatureSeenCell'; import { FeatureTypeCell } from 'component/common/Table/cells/FeatureTypeCell/FeatureTypeCell'; import { FeatureNameCell } from 'component/common/Table/cells/FeatureNameCell/FeatureNameCell'; import { DateCell } from 'component/common/Table/cells/DateCell/DateCell'; -import { ReportExpiredCell } from 'component/Reporting/ReportExpiredCell/ReportExpiredCell'; -import { ReportStatusCell } from 'component/Reporting/ReportStatusCell/ReportStatusCell'; -import { useMemo, useEffect } from 'react'; -import { - formatStatus, - ReportingStatus, -} from 'component/Reporting/ReportStatusCell/formatStatus'; -import { formatExpiredAt } from 'component/Reporting/ReportExpiredCell/formatExpiredAt'; import { FeatureStaleCell } from 'component/feature/FeatureToggleList/FeatureStaleCell/FeatureStaleCell'; -import theme from 'themes/theme'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { Search } from 'component/common/Search/Search'; +import { ReportExpiredCell } from './ReportExpiredCell/ReportExpiredCell'; +import { ReportStatusCell } from './ReportStatusCell/ReportStatusCell'; +import { formatStatus, ReportingStatus } from './ReportStatusCell/formatStatus'; +import { formatExpiredAt } from './ReportExpiredCell/formatExpiredAt'; interface IReportTableProps { projectId: string; @@ -44,13 +49,25 @@ export interface IReportTableRow { } export const ReportTable = ({ projectId, features }: IReportTableProps) => { + const theme = useTheme(); + const isExtraSmallScreen = useMediaQuery(theme.breakpoints.down('sm')); const isSmallScreen = useMediaQuery(theme.breakpoints.down('md')); + const isMediumScreen = useMediaQuery(theme.breakpoints.down('lg')); - const data: IReportTableRow[] = useMemo(() => { - return features.map(feature => { - return createReportTableRow(projectId, feature); - }); - }, [projectId, features]); + const data: IReportTableRow[] = useMemo( + () => + features.map(report => ({ + project: projectId, + name: report.name, + type: report.type, + stale: report.stale, + status: formatStatus(report), + lastSeenAt: report.lastSeenAt, + createdAt: report.createdAt, + expiredAt: formatExpiredAt(report), + })), + [projectId, features] + ); const initialState = useMemo( () => ({ @@ -61,8 +78,6 @@ export const ReportTable = ({ projectId, features }: IReportTableProps) => { ); const { - getTableProps, - getTableBodyProps, headerGroups, rows, prepareRow, @@ -80,49 +95,44 @@ export const ReportTable = ({ projectId, features }: IReportTableProps) => { disableSortRemove: true, }, useGlobalFilter, + useFlexLayout, useSortBy ); useEffect(() => { const hiddenColumns = []; + if (isMediumScreen) { + hiddenColumns.push('createdAt'); + } if (isSmallScreen) { - hiddenColumns.push('createdAt', 'expiredAt'); + hiddenColumns.push('expiredAt', 'lastSeenAt'); + } + if (isExtraSmallScreen) { + hiddenColumns.push('stale'); } setHiddenColumns(hiddenColumns); - }, [setHiddenColumns, isSmallScreen]); - - const header = ( - - } - /> - ); + }, [setHiddenColumns, isSmallScreen, isMediumScreen, isExtraSmallScreen]); return ( - + + } + /> + } + > - - - - {rows.map(row => { - prepareRow(row); - return ( - - {row.cells.map(cell => ( - - {cell.render('Cell')} - - ))} - - ); - })} - -
+
{ ); }; -const createReportTableRow = ( - projectId: string, - report: IFeatureToggleListItem -): IReportTableRow => { - return { - project: projectId, - name: report.name, - type: report.type, - stale: report.stale, - status: formatStatus(report), - lastSeenAt: report.lastSeenAt, - createdAt: report.createdAt, - expiredAt: formatExpiredAt(report), - }; -}; - const COLUMNS = [ { Header: 'Seen', @@ -173,7 +167,7 @@ const COLUMNS = [ align: 'center', Cell: FeatureSeenCell, disableGlobalFilter: true, - minWidth: 85, + maxWidth: 85, }, { Header: 'Type', @@ -181,36 +175,36 @@ const COLUMNS = [ align: 'center', Cell: FeatureTypeCell, disableGlobalFilter: true, - minWidth: 85, + maxWidth: 85, }, { Header: 'Name', accessor: 'name', - width: '60%', sortType: 'alphanumeric', Cell: FeatureNameCell, + minWidth: 120, }, { - Header: 'Created on', + Header: 'Created', accessor: 'createdAt', sortType: 'date', Cell: DateCell, disableGlobalFilter: true, - minWidth: 150, + maxWidth: 150, }, { Header: 'Expired', accessor: 'expiredAt', Cell: ReportExpiredCell, disableGlobalFilter: true, - minWidth: 150, + maxWidth: 150, }, { Header: 'Status', id: 'status', Cell: ReportStatusCell, disableGlobalFilter: true, - minWidth: 200, + width: 180, }, { Header: 'State', @@ -218,6 +212,6 @@ const COLUMNS = [ sortType: 'boolean', Cell: FeatureStaleCell, disableGlobalFilter: true, - minWidth: 120, + maxWidth: 120, }, ]; diff --git a/frontend/src/component/Reporting/utils.ts b/frontend/src/component/project/Project/ProjectHealth/ReportTable/utils.ts similarity index 100% rename from frontend/src/component/Reporting/utils.ts rename to frontend/src/component/project/Project/ProjectHealth/ReportTable/utils.ts diff --git a/frontend/src/component/project/Project/ProjectOverview.tsx b/frontend/src/component/project/Project/ProjectOverview.tsx index 83d2801a0a..df3fdb682f 100644 --- a/frontend/src/component/project/Project/ProjectOverview.tsx +++ b/frontend/src/component/project/Project/ProjectOverview.tsx @@ -2,17 +2,20 @@ import useProject from 'hooks/api/getters/useProject/useProject'; import { ProjectFeatureToggles } from './ProjectFeatureToggles/ProjectFeatureToggles'; import ProjectInfo from './ProjectInfo/ProjectInfo'; import { useStyles } from './Project.styles'; +import { usePageTitle } from 'hooks/usePageTitle'; interface IProjectOverviewProps { + projectName: string; projectId: string; } -const ProjectOverview = ({ projectId }: IProjectOverviewProps) => { +const ProjectOverview = ({ projectId, projectName }: IProjectOverviewProps) => { const { project, loading } = useProject(projectId, { refreshInterval: 15 * 1000, // ms }); const { members, features, health, description, environments } = project; const { classes: styles } = useStyles(); + usePageTitle(`Project overview – ${projectName}`); return (
diff --git a/frontend/src/component/project/ProjectAccess/ProjectAccess.tsx b/frontend/src/component/project/ProjectAccess/ProjectAccess.tsx index 85488d396c..7907f6d7e1 100644 --- a/frontend/src/component/project/ProjectAccess/ProjectAccess.tsx +++ b/frontend/src/component/project/ProjectAccess/ProjectAccess.tsx @@ -1,4 +1,4 @@ -import React, { useContext } from 'react'; +import React, { useContext, VFC } from 'react'; import { ProjectAccessPage } from 'component/project/ProjectAccess/ProjectAccessPage'; import { PageContent } from 'component/common/PageContent/PageContent'; import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; @@ -7,11 +7,17 @@ import { PageHeader } from 'component/common/PageHeader/PageHeader'; import AccessContext from 'contexts/AccessContext'; import { UPDATE_PROJECT } from 'component/providers/AccessProvider/permissions'; import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; +import { usePageTitle } from 'hooks/usePageTitle'; -export const ProjectAccess = () => { +interface IProjectAccess { + projectName: string; +} + +export const ProjectAccess: VFC = ({ projectName }) => { const projectId = useRequiredPathParam('projectId'); const { hasAccess } = useContext(AccessContext); const { isOss } = useUiConfig(); + usePageTitle(`Project access – ${projectName}`); if (isOss()) { return ( diff --git a/frontend/src/component/project/ProjectAccess/ProjectAccessPage.tsx b/frontend/src/component/project/ProjectAccess/ProjectAccessPage.tsx index 27984c877d..05888265c5 100644 --- a/frontend/src/component/project/ProjectAccess/ProjectAccessPage.tsx +++ b/frontend/src/component/project/ProjectAccess/ProjectAccessPage.tsx @@ -70,7 +70,7 @@ export const ProjectAccessPage = () => { return ( } + header={} className={styles.pageContent} > diff --git a/frontend/src/component/project/ProjectAccess/ProjectAccessTable/ProjectAccessTable.tsx b/frontend/src/component/project/ProjectAccess/ProjectAccessTable/ProjectAccessTable.tsx index 3def4e1cdb..d55f0784a3 100644 --- a/frontend/src/component/project/ProjectAccess/ProjectAccessTable/ProjectAccessTable.tsx +++ b/frontend/src/component/project/ProjectAccess/ProjectAccessTable/ProjectAccessTable.tsx @@ -7,7 +7,7 @@ import { TableCell, SortableTableHeader, } from 'component/common/Table'; -import { Avatar, Box, SelectChangeEvent } from '@mui/material'; +import { Avatar, SelectChangeEvent } from '@mui/material'; import { Delete } from '@mui/icons-material'; import { sortTypes } from 'utils/sortTypes'; import { @@ -18,6 +18,7 @@ import { ProjectRoleCell } from './ProjectRoleCell/ProjectRoleCell'; import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton'; import { UPDATE_PROJECT } from 'component/providers/AccessProvider/permissions'; import { TextCell } from 'component/common/Table/cells/TextCell/TextCell'; +import { ActionCell } from 'component/common/Table/cells/ActionCell/ActionCell'; const initialState = { sortBy: [{ id: 'name' }], @@ -94,16 +95,10 @@ export const ProjectAccessTable: VFC = ({ align: 'center', width: 80, Cell: ({ row: { original: user } }: any) => ( - + handleRemoveAccess(user)} disabled={access.users.length === 1} tooltipProps={{ @@ -115,7 +110,7 @@ export const ProjectAccessTable: VFC = ({ > - + ), }, ], diff --git a/frontend/src/component/project/ProjectEnvironment/ProjectEnvironment.tsx b/frontend/src/component/project/ProjectEnvironment/ProjectEnvironment.tsx index b55ba70ebb..18f12f4712 100644 --- a/frontend/src/component/project/ProjectEnvironment/ProjectEnvironment.tsx +++ b/frontend/src/component/project/ProjectEnvironment/ProjectEnvironment.tsx @@ -18,14 +18,18 @@ import { IProjectEnvironment } from 'interfaces/environments'; import { getEnabledEnvs } from './helpers'; import StringTruncator from 'component/common/StringTruncator/StringTruncator'; import { useThemeStyles } from 'themes/themeStyles'; +import { usePageTitle } from 'hooks/usePageTitle'; interface IProjectEnvironmentListProps { projectId: string; + projectName: string; } const ProjectEnvironmentList = ({ projectId, + projectName, }: IProjectEnvironmentListProps) => { + usePageTitle(`Project environments – ${projectName}`); // api state const [envs, setEnvs] = useState([]); const { setToastData, setToastApiError } = useToast(); @@ -176,7 +180,7 @@ const ProjectEnvironmentList = ({ } isLoading={loading} diff --git a/frontend/src/hooks/usePageTitle.ts b/frontend/src/hooks/usePageTitle.ts index d37600de07..41c548058b 100644 --- a/frontend/src/hooks/usePageTitle.ts +++ b/frontend/src/hooks/usePageTitle.ts @@ -1,18 +1,20 @@ import { useEffect, useContext } from 'react'; import { AnnouncerContext } from 'component/common/Announcer/AnnouncerContext/AnnouncerContext'; -export const usePageTitle = (title: string) => { +export const usePageTitle = (title?: string) => { const { setAnnouncement } = useContext(AnnouncerContext); useEffect(() => { - document.title = title; - return () => { - document.title = DEFAULT_PAGE_TITLE; - }; + if (title) { + document.title = title; + return () => { + document.title = DEFAULT_PAGE_TITLE; + }; + } }, [title]); useEffect(() => { - if (title !== DEFAULT_PAGE_TITLE) { + if (title && title !== DEFAULT_PAGE_TITLE) { setAnnouncement(`Navigated to ${title}`); } }, [setAnnouncement, title]);