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;
+}