From dc1847420c3295f850c0b489f464e580fd017a44 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Fri, 1 Nov 2024 10:08:51 +0100 Subject: [PATCH] feat: use actionable change request data in UI (#8613) This PR hooks up the actionable change request data to the counter in the UI. It: - creates a getter for the data. It only exposes data. We don't really care about error or loading for this (it's not an important piece of data), so we don't expose that just yet. - Adds orval-generated schema - Uses the hook in the UI. It also stwitches the previous "notification badge" for MUI's built-in badge. We already use that badge component for the event timeline, so I thought it would make sense to do it here too. Overall, the effect is pretty good, but there's a few kinks we might wanna work out. I'll make a follow-up for that (worked out in this PR after all) --- .../src/component/project/Project/Project.tsx | 76 +++++++++---------- .../useActionableChangeRequests.ts | 30 ++++++++ .../models/actionableChangeRequestsSchema.ts | 16 ++++ 3 files changed, 81 insertions(+), 41 deletions(-) create mode 100644 frontend/src/hooks/api/getters/useActionableChangeRequests/useActionableChangeRequests.ts create mode 100644 frontend/src/openapi/models/actionableChangeRequestsSchema.ts 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; +}