From a3e4c1095d18be0ce7e1b58a6ac1edffb09e495d Mon Sep 17 00:00:00 2001 From: FredrikOseberg Date: Thu, 23 Oct 2025 10:44:47 +0200 Subject: [PATCH] refactor: better type safety --- .../Change/ConsolidatedProgressionChanges.tsx | 41 ++++++--- .../Changes/Change/MilestoneListRenderer.tsx | 83 +++++++++++++++---- .../Changes/Change/ProgressionChange.tsx | 38 ++++++--- .../ReleasePlanMilestoneItem.tsx | 4 +- ...hanges.ts => pendingProgressionChanges.ts} | 2 +- 5 files changed, 123 insertions(+), 45 deletions(-) rename frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestoneItem/{usePendingProgressionChanges.ts => pendingProgressionChanges.ts} (96%) diff --git a/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/ConsolidatedProgressionChanges.tsx b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/ConsolidatedProgressionChanges.tsx index 330c56608c..9e0a78cbd0 100644 --- a/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/ConsolidatedProgressionChanges.tsx +++ b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/ConsolidatedProgressionChanges.tsx @@ -15,7 +15,10 @@ import { Deleted, } from './Change.styles.tsx'; import type { ChangeMilestoneProgressionSchema } from 'openapi'; -import { MilestoneListRenderer } from './MilestoneListRenderer.tsx'; +import { + ReadonlyMilestoneListRenderer, + EditableMilestoneListRenderer, +} from './MilestoneListRenderer.tsx'; import { applyProgressionChanges } from './applyProgressionChanges.js'; import { EventDiff } from 'component/events/EventDiff/EventDiff'; @@ -72,11 +75,11 @@ export const ConsolidatedProgressionChanges: FC<{ feature: IChangeRequestFeature; currentReleasePlan?: IReleasePlan; changeRequestState: ChangeRequestState; - onUpdateChangeRequestSubmit?: ( + onUpdateChangeRequestSubmit: ( sourceMilestoneId: string, payload: ChangeMilestoneProgressionSchema, ) => Promise; - onDeleteChangeRequestSubmit?: (sourceMilestoneId: string) => Promise; + onDeleteChangeRequestSubmit: (sourceMilestoneId: string) => Promise; }> = ({ feature, currentReleasePlan, @@ -113,6 +116,9 @@ export const ConsolidatedProgressionChanges: FC<{ basePlan, ); + const readonly = + changeRequestState === 'Applied' || changeRequestState === 'Cancelled'; + return ( @@ -137,16 +143,25 @@ export const ConsolidatedProgressionChanges: FC<{ - + {readonly ? ( + + ) : ( + + )} ({ marginLeft: theme.spacing(3.25), })); -interface MilestoneListRendererProps { +interface MilestoneListRendererCoreProps { plan: IReleasePlan; - changeRequestState: ChangeRequestState; - milestonesWithAutomation?: Set; - milestonesWithDeletedAutomation?: Set; - onUpdateAutomation?: ( + readonly: boolean; + milestonesWithAutomation: Set; + milestonesWithDeletedAutomation: Set; + onUpdateAutomation: ( sourceMilestoneId: string, payload: ChangeMilestoneProgressionSchema, ) => Promise; - onDeleteAutomation?: (sourceMilestoneId: string) => void; + onDeleteAutomation: (sourceMilestoneId: string) => void; } -export const MilestoneListRenderer = ({ +const MilestoneListRendererCore = ({ plan, - changeRequestState, - milestonesWithAutomation = new Set(), - milestonesWithDeletedAutomation = new Set(), + readonly, + milestonesWithAutomation, + milestonesWithDeletedAutomation, onUpdateAutomation, onDeleteAutomation, -}: MilestoneListRendererProps) => { - // TODO: Split into read and write model at the type level to avoid having optional handlers - const readonly = - changeRequestState === 'Applied' || changeRequestState === 'Cancelled'; +}: MilestoneListRendererCoreProps) => { const status: MilestoneStatus = 'not-started'; return ( @@ -70,14 +66,14 @@ export const MilestoneListRenderer = ({ } targetMilestoneId={nextMilestoneId} onSave={async (payload) => { - await onUpdateAutomation?.( + await onUpdateAutomation( milestone.id, payload, ); return { shouldReset: true }; }} onDelete={() => - onDeleteAutomation?.(milestone.id) + onDeleteAutomation(milestone.id) } milestoneName={milestone.name} status={status} @@ -102,3 +98,56 @@ export const MilestoneListRenderer = ({ ); }; + +interface ReadonlyMilestoneListRendererProps { + plan: IReleasePlan; + milestonesWithAutomation?: Set; + milestonesWithDeletedAutomation?: Set; +} + +export const ReadonlyMilestoneListRenderer = ({ + plan, + milestonesWithAutomation = new Set(), + milestonesWithDeletedAutomation = new Set(), +}: ReadonlyMilestoneListRendererProps) => { + return ( + {}} + onDeleteAutomation={() => {}} + /> + ); +}; + +interface EditableMilestoneListRendererProps { + plan: IReleasePlan; + milestonesWithAutomation?: Set; + milestonesWithDeletedAutomation?: Set; + onUpdateAutomation: ( + sourceMilestoneId: string, + payload: ChangeMilestoneProgressionSchema, + ) => Promise; + onDeleteAutomation: (sourceMilestoneId: string) => void; +} + +export const EditableMilestoneListRenderer = ({ + plan, + milestonesWithAutomation = new Set(), + milestonesWithDeletedAutomation = new Set(), + onUpdateAutomation, + onDeleteAutomation, +}: EditableMilestoneListRendererProps) => { + return ( + + ); +}; diff --git a/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/ProgressionChange.tsx b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/ProgressionChange.tsx index 960302c2e3..88d5896536 100644 --- a/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/ProgressionChange.tsx +++ b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/ProgressionChange.tsx @@ -10,7 +10,10 @@ import { EventDiff } from 'component/events/EventDiff/EventDiff'; import { Tab, TabList, TabPanel, Tabs } from './ChangeTabComponents.tsx'; import { Action, ChangeItemInfo, ChangeItemWrapper } from './Change.styles.tsx'; import { styled } from '@mui/material'; -import { MilestoneListRenderer } from './MilestoneListRenderer.tsx'; +import { + ReadonlyMilestoneListRenderer, + EditableMilestoneListRenderer, +} from './MilestoneListRenderer.tsx'; import { applyProgressionChanges } from './applyProgressionChanges.ts'; const StyledTabs = styled(Tabs)(({ theme }) => ({ @@ -24,11 +27,11 @@ interface ProgressionChangeProps { currentReleasePlan?: IReleasePlan; actions?: ReactNode; changeRequestState: ChangeRequestState; - onUpdateChangeRequestSubmit?: ( + onUpdateChangeRequestSubmit: ( sourceMilestoneId: string, payload: ChangeMilestoneProgressionSchema, ) => Promise; - onDeleteChangeRequestSubmit?: (sourceMilestoneId: string) => void; + onDeleteChangeRequestSubmit: (sourceMilestoneId: string) => void; } export const ProgressionChange: FC = ({ @@ -62,6 +65,9 @@ export const ProgressionChange: FC = ({ (milestone) => milestone.id === sourceId, ); + const readonly = + changeRequestState === 'Applied' || changeRequestState === 'Cancelled'; + return ( @@ -80,15 +86,23 @@ export const ProgressionChange: FC = ({ - + {readonly ? ( + + ) : ( + + )} {