diff --git a/frontend/src/component/project/Project/Project.tsx b/frontend/src/component/project/Project/Project.tsx index 0252fc24c7..029624a29d 100644 --- a/frontend/src/component/project/Project/Project.tsx +++ b/frontend/src/component/project/Project/Project.tsx @@ -14,10 +14,22 @@ import { StyledTabContainer, StyledTopRow, } from './Project.styles'; -import { Box, Paper, Tabs, Typography, styled } from '@mui/material'; +import { + Badge as CounterBadge, + Box, + Paper, + Tabs, + Typography, + styled, +} from '@mui/material'; import useToast from 'hooks/useToast'; import useQueryParams from 'hooks/useQueryParams'; -import { useEffect, useState } from 'react'; +import { + type PropsWithChildren, + useEffect, + useState, + type ReactNode, +} from 'react'; import ProjectEnvironment from '../ProjectEnvironment/ProjectEnvironment'; import { ProjectFeaturesArchive } from './ProjectFeaturesArchive/ProjectFeaturesArchive'; import ProjectFlags from './ProjectFlags'; @@ -45,8 +57,8 @@ import { ProjectInsights } from './ProjectInsights/ProjectInsights'; import useProjectOverview from 'hooks/api/getters/useProjectOverview/useProjectOverview'; import { ProjectArchived } from './ArchiveProject/ProjectArchived'; import { usePlausibleTracker } from '../../../hooks/usePlausibleTracker'; -import { ScreenReaderOnly } from 'component/common/ScreenReaderOnly/ScreenReaderOnly'; import { useUiFlag } from 'hooks/useUiFlag'; +import { useActionableChangeRequests } from 'hooks/api/getters/useActionableChangeRequests/useActionableChangeRequests'; const StyledBadge = styled(Badge)(({ theme }) => ({ position: 'absolute', @@ -64,46 +76,32 @@ interface ITab { flag?: keyof UiFlags; new?: boolean; isEnterprise?: boolean; + label?: () => ReactNode; } -const CircleContainer = styled('div')(({ theme }) => ({ - position: 'absolute', - width: theme.spacing(2.5), - height: theme.spacing(2.5), - display: 'grid', - placeItems: 'center', - borderRadius: '50%', - - background: theme.palette.background.alternative, - color: theme.palette.primary.contrastText, - fontSize: theme.typography.body2.fontSize, - - // todo: revisit these values later - top: 10, - right: 0, - [theme.breakpoints.down('md')]: { - top: 2, +const StyledCounterBadge = styled(CounterBadge)(({ theme }) => ({ + '.MuiBadge-badge': { + backgroundColor: theme.palette.background.alternative, + right: '2px', }, + flex: 'auto', + justifyContent: 'center', + minHeight: '1.5em', + alignItems: 'center', })); -const ActionableChangeRequestsIndicator = () => { - // todo: useSWR for this instead (maybe conditional) - const count = 0; +const TabText = styled('span')(({ theme }) => ({ + color: theme.palette.text.primary, +})); - if (count <= 0) { - return null; - } - - const renderedCount = count > 9 ? '9+' : count; +const ChangeRequestsLabel = () => { + const projectId = useRequiredPathParam('projectId'); + const { total } = useActionableChangeRequests(projectId); return ( - - You can move - {renderedCount} - - change requests into their next phase. - - + + Change requests + ); }; @@ -160,6 +158,7 @@ export const Project = () => { path: `${basePath}/change-requests`, name: 'change-request', isEnterprise: true, + label: simplifyProjectOverview ? ChangeRequestsLabel : undefined, }, { title: 'Applications', @@ -300,7 +299,7 @@ export const Project = () => { { if (tab.title !== 'Flags') { @@ -329,11 +328,6 @@ export const Project = () => { } /> - {simplifyProjectOverview && - tab.name === - 'change-request' && ( - - )} {(tab.isEnterprise && isPro() && enterpriseIcon) || diff --git a/frontend/src/hooks/api/getters/useActionableChangeRequests/useActionableChangeRequests.ts b/frontend/src/hooks/api/getters/useActionableChangeRequests/useActionableChangeRequests.ts new file mode 100644 index 0000000000..59f59238a1 --- /dev/null +++ b/frontend/src/hooks/api/getters/useActionableChangeRequests/useActionableChangeRequests.ts @@ -0,0 +1,30 @@ +import { formatApiPath } from 'utils/formatPath'; +import handleErrorResponses from '../httpErrorResponseHandler'; +import type { ActionableChangeRequestsSchema } from 'openapi/models/actionableChangeRequestsSchema'; +import { useEnterpriseSWR } from '../useEnterpriseSWR/useEnterpriseSWR'; + +interface IUseActionableChangeRequestsOutput { + total?: number; +} + +export const useActionableChangeRequests = ( + projectId: string, +): IUseActionableChangeRequestsOutput => { + const { data } = useEnterpriseSWR( + { total: 0 }, + formatApiPath( + `api/admin/projects/${projectId}/change-requests/actionable`, + ), + fetcher, + ); + + return { + total: data?.total, + }; +}; + +const fetcher = (path: string) => { + return fetch(path) + .then(handleErrorResponses('Actionable change requests')) + .then((res) => res.json()); +}; diff --git a/frontend/src/openapi/models/actionableChangeRequestsSchema.ts b/frontend/src/openapi/models/actionableChangeRequestsSchema.ts new file mode 100644 index 0000000000..3e2805bdb0 --- /dev/null +++ b/frontend/src/openapi/models/actionableChangeRequestsSchema.ts @@ -0,0 +1,16 @@ +/** + * Generated by Orval + * Do not edit manually. + * See `gen:api` script in package.json + */ + +/** + * Data related to actionable change requests in a project. + */ +export interface ActionableChangeRequestsSchema { + /** + * The number of actionable change requests in the project. + * @minimum 0 + */ + total: number; +}