diff --git a/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/MilestoneListRenderer.tsx b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/MilestoneListRenderer.tsx index b2167f3b71..0ba98d7c46 100644 --- a/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/MilestoneListRenderer.tsx +++ b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/MilestoneListRenderer.tsx @@ -70,6 +70,7 @@ const MilestoneListRendererCore = ({ .intervalMinutes } targetMilestoneId={nextMilestoneId} + sourceMilestoneStartedAt={milestone.startedAt} onSave={async (payload) => { await onUpdateAutomation( milestone.id, diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/MilestoneProgressionForm/MilestoneProgressionForm.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/MilestoneProgressionForm/MilestoneProgressionForm.tsx index 3e11b58f40..8f17fd52df 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/MilestoneProgressionForm/MilestoneProgressionForm.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/MilestoneProgressionForm/MilestoneProgressionForm.tsx @@ -3,6 +3,7 @@ import BoltIcon from '@mui/icons-material/Bolt'; import { useMilestoneProgressionForm } from '../hooks/useMilestoneProgressionForm.js'; import { MilestoneProgressionTimeInput } from './MilestoneProgressionTimeInput.tsx'; import type { ChangeMilestoneProgressionSchema } from 'openapi'; +import type { MilestoneStatus } from '../ReleasePlanMilestone/ReleasePlanMilestoneStatus.tsx'; const StyledFormContainer = styled('div')(({ theme }) => ({ display: 'flex', @@ -49,12 +50,14 @@ const StyledButtonGroup = styled('div')(({ theme }) => ({ const StyledErrorMessage = styled('span')(({ theme }) => ({ color: theme.palette.error.main, fontSize: theme.typography.body2.fontSize, - marginRight: 'auto', + paddingLeft: theme.spacing(3.25), })); interface IMilestoneProgressionFormProps { sourceMilestoneId: string; targetMilestoneId: string; + sourceMilestoneStartedAt?: string | null; + status?: MilestoneStatus; onSubmit: ( payload: ChangeMilestoneProgressionSchema, ) => Promise<{ shouldReset?: boolean }>; @@ -64,12 +67,17 @@ interface IMilestoneProgressionFormProps { export const MilestoneProgressionForm = ({ sourceMilestoneId, targetMilestoneId, + sourceMilestoneStartedAt, + status, onSubmit, onCancel, }: IMilestoneProgressionFormProps) => { const form = useMilestoneProgressionForm( sourceMilestoneId, targetMilestoneId, + {}, + sourceMilestoneStartedAt, + status, ); const handleSubmit = async () => { @@ -102,10 +110,10 @@ export const MilestoneProgressionForm = ({ onTimeUnitChange={form.handleTimeUnitChange} /> + {form.errors.time && ( + {form.errors.time} + )} - {form.errors.time && ( - {form.errors.time} - )} 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 9beaf268fd..fe405bb91b 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/MilestoneNextStartTime.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/MilestoneNextStartTime.tsx @@ -1,12 +1,12 @@ import { styled } from '@mui/material'; import HourglassEmptyOutlinedIcon from '@mui/icons-material/HourglassEmptyOutlined'; import type { IReleasePlanMilestone } from 'interfaces/releasePlans'; -import { formatDateYMDHMS } from 'utils/formatDate'; +import { formatDateYMDHM } from 'utils/formatDate'; import { isToday, isTomorrow, format } from 'date-fns'; import { calculateMilestoneStartTime } from '../utils/calculateMilestoneStartTime.ts'; import { useUiFlag } from 'hooks/useUiFlag'; -const formatSmartDate = (date: Date): string => { +export const formatSmartDate = (date: Date): string => { const timeString = format(date, 'HH:mm'); if (isToday(date)) { @@ -17,7 +17,7 @@ const formatSmartDate = (date: Date): string => { } // For other dates, show full date with time - return formatDateYMDHMS(date); + return formatDateYMDHM(date); }; const StyledTimeContainer = styled('span')(({ theme }) => ({ @@ -74,7 +74,7 @@ export const MilestoneNextStartTime = ({ ); const text = projectedStartTime - ? `Starting ${formatSmartDate(projectedStartTime)}` + ? `Starting after ${formatSmartDate(projectedStartTime)}` : 'Waiting to start'; return ( 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 2afed24fdc..1bfe825e50 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/MilestoneTransitionDisplay.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/MilestoneTransitionDisplay.tsx @@ -90,9 +90,16 @@ const StyledButtonGroup = styled('div', { }), })); +const StyledErrorMessage = styled('span')(({ theme }) => ({ + color: theme.palette.error.main, + fontSize: theme.typography.body2.fontSize, + paddingLeft: theme.spacing(3.25), +})); + interface IMilestoneTransitionDisplayProps { intervalMinutes: number; targetMilestoneId: string; + sourceMilestoneStartedAt?: string | null; onSave: ( payload: ChangeMilestoneProgressionSchema, ) => Promise<{ shouldReset?: boolean }>; @@ -105,6 +112,7 @@ interface IMilestoneTransitionDisplayProps { export const MilestoneTransitionDisplay = ({ intervalMinutes, targetMilestoneId, + sourceMilestoneStartedAt, onSave, onDelete, milestoneName, @@ -119,6 +127,8 @@ export const MilestoneTransitionDisplay = ({ timeValue: initial.value, timeUnit: initial.unit, }, + sourceMilestoneStartedAt, + status, ); const currentIntervalMinutes = form.getIntervalMinutes(); @@ -130,9 +140,19 @@ export const MilestoneTransitionDisplay = ({ form.setTimeUnit(newInitial.unit); }, [intervalMinutes]); + useEffect(() => { + if (!hasChanged) { + form.clearErrors(); + } + }, [hasChanged, form.clearErrors]); + const handleSave = async () => { if (!hasChanged) return; + if (!form.validate()) { + return; + } + const payload: ChangeMilestoneProgressionSchema = { targetMilestone: targetMilestoneId, transitionCondition: { @@ -151,6 +171,7 @@ export const MilestoneTransitionDisplay = ({ const initial = getTimeValueAndUnitFromMinutes(intervalMinutes); form.setTimeValue(initial.value); form.setTimeUnit(initial.unit); + form.clearErrors(); }; const handleKeyDown = (event: React.KeyboardEvent) => { @@ -192,6 +213,9 @@ export const MilestoneTransitionDisplay = ({ )} + {form.errors.time && ( + {form.errors.time} + )} {hasChanged && (