diff --git a/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/MilestoneListRenderer.tsx b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/MilestoneListRenderer.tsx index 9afe800d8f..72235954c6 100644 --- a/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/MilestoneListRenderer.tsx +++ b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/MilestoneListRenderer.tsx @@ -37,7 +37,7 @@ const MilestoneListRendererCore = ({ onUpdateAutomation, onDeleteAutomation, }: MilestoneListRendererCoreProps) => { - const status: MilestoneStatus = 'not-started'; + const status: MilestoneStatus = { type: 'not-started' }; return ( <> diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/MilestoneAutomationSection.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/MilestoneAutomationSection.tsx index 8e87ad43fb..db93e17415 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/MilestoneAutomationSection.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/MilestoneAutomationSection.tsx @@ -4,13 +4,13 @@ import type { MilestoneStatus } from './ReleasePlanMilestoneStatus.tsx'; const StyledAutomationContainer = styled('div', { shouldForwardProp: (prop) => prop !== 'status', })<{ status?: MilestoneStatus }>(({ theme, status }) => ({ - border: `${status === 'active' ? '1.5px' : '1px'} solid ${status === 'active' ? theme.palette.success.border : theme.palette.divider}`, + border: `${status?.type === 'active' ? '1.5px' : '1px'} solid ${status?.type === 'active' ? theme.palette.success.border : theme.palette.divider}`, borderTop: `1px solid ${theme.palette.divider}`, borderRadius: `0 0 ${theme.shape.borderRadiusLarge}px ${theme.shape.borderRadiusLarge}px`, padding: theme.spacing(1.5, 2), paddingLeft: theme.spacing(2.25), backgroundColor: - status === 'completed' + status?.type === 'completed' ? theme.palette.background.default : theme.palette.background.paper, display: 'flex', diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/MilestoneNextStartTime.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/MilestoneNextStartTime.tsx index b8972784bc..2a20b51486 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/MilestoneNextStartTime.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/MilestoneNextStartTime.tsx @@ -1,9 +1,8 @@ import { styled } from '@mui/material'; import HourglassEmptyOutlinedIcon from '@mui/icons-material/HourglassEmptyOutlined'; -import type { IReleasePlanMilestone } from 'interfaces/releasePlans'; import { isToday, isTomorrow, format, addMinutes } from 'date-fns'; -import { calculateMilestoneStartTime } from '../utils/calculateMilestoneStartTime.ts'; import { useUiFlag } from 'hooks/useUiFlag'; +import type { MilestoneStatus } from './ReleasePlanMilestoneStatus.tsx'; export const formatSmartDate = (date: Date): string => { const startTime = format(date, 'HH:mm'); @@ -40,15 +39,11 @@ const StyledHourglassIcon = styled(HourglassEmptyOutlinedIcon)(({ theme }) => ({ })); interface IMilestoneNextStartTimeProps { - milestone: IReleasePlanMilestone; - allMilestones: IReleasePlanMilestone[]; - activeMilestoneId?: string; + status: MilestoneStatus; } export const MilestoneNextStartTime = ({ - milestone, - allMilestones, - activeMilestoneId, + status, }: IMilestoneNextStartTimeProps) => { const milestoneProgressionEnabled = useUiFlag('milestoneProgression'); @@ -56,24 +51,12 @@ export const MilestoneNextStartTime = ({ return null; } - const activeIndex = allMilestones.findIndex( - (milestone) => milestone.id === activeMilestoneId, - ); - const currentIndex = allMilestones.findIndex((m) => m.id === milestone.id); - - const isActiveMilestone = milestone.id === activeMilestoneId; - const isBehindActiveMilestone = - activeIndex !== -1 && currentIndex !== -1 && currentIndex < activeIndex; - - if (isActiveMilestone || isBehindActiveMilestone) { + // Only show for not-started milestones with scheduledAt + if (status.type !== 'not-started' || !status.scheduledAt) { return null; } - const projectedStartTime = calculateMilestoneStartTime( - allMilestones, - milestone.id, - activeMilestoneId, - ); + const projectedStartTime = status.scheduledAt; if (!projectedStartTime) return null; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/MilestoneTransitionDisplay.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/MilestoneTransitionDisplay.tsx index ab4a027db1..3b8dedbb67 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/MilestoneTransitionDisplay.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/MilestoneTransitionDisplay.tsx @@ -53,7 +53,7 @@ const StyledIcon = styled(BoltIcon, { fontSize: 18, flexShrink: 0, backgroundColor: - status === 'completed' + status?.type === 'completed' ? theme.palette.neutral.border : theme.palette.primary.main, borderRadius: '50%', @@ -64,7 +64,7 @@ const StyledLabel = styled('span', { shouldForwardProp: (prop) => prop !== 'status', })<{ status?: MilestoneStatus }>(({ theme, status }) => ({ color: - status === 'completed' + status?.type === 'completed' ? theme.palette.text.secondary : theme.palette.text.primary, fontSize: theme.typography.body2.fontSize, diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/ReleasePlanMilestone.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/ReleasePlanMilestone.tsx index eafa04bd0c..6c5581cdb0 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/ReleasePlanMilestone.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/ReleasePlanMilestone.tsx @@ -23,15 +23,15 @@ const StyledAccordion = styled(Accordion, { shouldForwardProp: (prop) => prop !== 'status' && prop !== 'hasAutomation', })<{ status: MilestoneStatus; hasAutomation?: boolean }>( ({ theme, status, hasAutomation }) => ({ - border: `${status === 'active' ? '1.5px' : '1px'} solid ${status === 'active' ? theme.palette.success.border : theme.palette.divider}`, + border: `${status.type === 'active' ? '1.5px' : '1px'} solid ${status.type === 'active' ? theme.palette.success.border : theme.palette.divider}`, borderBottom: hasAutomation ? 'none' - : `${status === 'active' ? '1.5px' : '1px'} solid ${status === 'active' ? theme.palette.success.border : theme.palette.divider}`, + : `${status.type === 'active' ? '1.5px' : '1px'} solid ${status.type === 'active' ? theme.palette.success.border : theme.palette.divider}`, overflow: 'hidden', boxShadow: 'none', margin: 0, backgroundColor: - status === 'completed' + status.type === 'completed' ? theme.palette.background.default : theme.palette.background.paper, borderRadius: hasAutomation @@ -68,7 +68,7 @@ const StyledTitle = styled('span', { })<{ status?: MilestoneStatus }>(({ theme, status }) => ({ fontWeight: theme.fontWeight.bold, color: - status === 'completed' + status?.type === 'completed' ? theme.palette.text.secondary : theme.palette.text.primary, })); @@ -110,7 +110,7 @@ interface IReleasePlanMilestoneProps { export const ReleasePlanMilestone = ({ milestone, - status = 'not-started', + status = { type: 'not-started' }, onStartMilestone, readonly, automationSection, @@ -133,15 +133,12 @@ export const ReleasePlanMilestone = ({ {milestone.name} {(!readonly && onStartMilestone) || - (status === 'active' && milestone.startedAt) ? ( + (status.type === 'active' && + milestone.startedAt) ? ( {!readonly && ( )} {!readonly && onStartMilestone && ( @@ -152,7 +149,7 @@ export const ReleasePlanMilestone = ({ } /> )} - {status === 'active' && + {status.type === 'active' && milestone.startedAt && ( Started{' '} @@ -188,14 +185,10 @@ export const ReleasePlanMilestone = ({ {milestone.name} {(!readonly && onStartMilestone) || - (status === 'active' && milestone.startedAt) ? ( + (status.type === 'active' && milestone.startedAt) ? ( {!readonly && ( - + )} {!readonly && onStartMilestone && ( )} - {status === 'active' && milestone.startedAt && ( - - Started{' '} - {formatDateYMDHMS(milestone.startedAt)} - - )} + {status.type === 'active' && + milestone.startedAt && ( + + Started{' '} + {formatDateYMDHMS( + milestone.startedAt, + )} + + )} ) : null} diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/ReleasePlanMilestoneStatus.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/ReleasePlanMilestoneStatus.tsx index 762c625fe2..e3386a39d4 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/ReleasePlanMilestoneStatus.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/ReleasePlanMilestoneStatus.tsx @@ -4,7 +4,11 @@ import PauseCircleIcon from '@mui/icons-material/PauseCircle'; import TripOriginIcon from '@mui/icons-material/TripOrigin'; import { useUiFlag } from 'hooks/useUiFlag'; -export type MilestoneStatus = 'not-started' | 'active' | 'paused' | 'completed'; +export type MilestoneStatus = + | { type: 'not-started'; scheduledAt?: Date } + | { type: 'active' } + | { type: 'paused' } + | { type: 'completed' }; const StyledStatusButton = styled('button', { shouldForwardProp: (prop) => prop !== 'status', @@ -18,7 +22,7 @@ const StyledStatusButton = styled('button', { paddingRight: theme.spacing(1), cursor: 'pointer', backgroundColor: - status === 'active' + status.type === 'active' ? theme.palette.success.light : theme.palette.neutral.light, '&:focus-visible': { @@ -26,9 +30,9 @@ const StyledStatusButton = styled('button', { }, '&:hover': { backgroundColor: - status === 'active' + status.type === 'active' ? theme.palette.success.light - : status === 'paused' + : status.type === 'paused' ? 'transparent' : theme.palette.neutral.light, textDecoration: 'none', @@ -38,18 +42,18 @@ const StyledStatusButton = styled('button', { fontWeight: theme.fontWeight.medium, borderRadius: theme.shape.borderRadiusMedium, color: - status === 'active' + status.type === 'active' ? theme.palette.success.contrastText - : status === 'paused' + : status.type === 'paused' ? theme.palette.text.primary : theme.palette.primary.main, '& svg': { color: - status === 'active' + status.type === 'active' ? theme.palette.success.main - : status === 'paused' + : status.type === 'paused' ? theme.palette.text.disabled - : status === 'completed' + : status.type === 'completed' ? theme.palette.neutral.border : theme.palette.primary.main, height: theme.spacing(3), @@ -70,7 +74,7 @@ const getStatusText = ( status: MilestoneStatus, progressionsEnabled: boolean, ): string => { - switch (status) { + switch (status.type) { case 'active': return 'Running'; case 'paused': @@ -83,7 +87,7 @@ const getStatusText = ( }; const getStatusIcon = (status: MilestoneStatus) => { - switch (status) { + switch (status.type) { case 'active': return ; case 'paused': @@ -101,7 +105,7 @@ export const ReleasePlanMilestoneStatus = ({ const statusText = getStatusText(status, milestoneProgressionsEnabled); const statusIcon = getStatusIcon(status); - const disabled = status === 'active' || status === 'paused'; + const disabled = status.type === 'active' || status.type === 'paused'; return ( { if (milestone.id === activeMilestoneId) { - return environmentIsDisabled ? 'paused' : 'active'; + return environmentIsDisabled ? { type: 'paused' } : { type: 'active' }; } if (index < activeIndex) { - return 'completed'; + return { type: 'completed' }; } - return 'not-started'; + const scheduledAt = calculateMilestoneStartTime( + allMilestones, + milestone.id, + activeMilestoneId, + ); + + return { + type: 'not-started', + scheduledAt: scheduledAt || undefined, + }; }; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/hooks/useMilestoneProgressionForm.ts b/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/hooks/useMilestoneProgressionForm.ts index 6b82b4f3c1..9fb16ee5d5 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/hooks/useMilestoneProgressionForm.ts +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/hooks/useMilestoneProgressionForm.ts @@ -88,7 +88,8 @@ export const useMilestoneProgressionForm = ( if ( sourceMilestoneStartedAt && total > 0 && - (status === 'active' || status === 'paused') + status && + (status.type === 'active' || status.type === 'paused') ) { const startDate = new Date(sourceMilestoneStartedAt); const nextMilestoneDate = addMinutes(startDate, total);