Problem
console/src/app/Actions/Scheduler/components/ModalCreateAction.tsx is a 345-line component that handles the entire action creation flow in a single file:
- Operation selection (lines 66-74, 208-228): Scan / Power On / Power Off toggle group
- Account selection (lines 76-97): fetch accounts on open,
AccountTypeaheadSelect wrapper
- Cluster selection (lines 99-126): fetch clusters when account changes,
ClusterTypeaheadSelect wrapper
- Execution configuration (lines 128-141, 251-328): instant vs scheduled vs cron, datetime picker, cron input with validation
- Form validation (lines 49-64): cross-field validation combining operation, target, and execution state
- Action submission (lines 143-171): build request object, API call, error handling
All state (11 useState calls) and 3 useEffect calls live at the top level. The component is hard to reason about because changes to any section require understanding the full state tree.
Current State Map
ModalCreateAction
├── State (11 vars):
│ ├── actionOperation, actionType, description — what to do
│ ├── selectedAccount, allAccounts — account target
│ ├── selectedCluster, allClusters — cluster target
│ ├── scheduledDateTime, showSchedule — when (scheduled)
│ ├── cronExpression, cronTouched — when (cron)
│ └── submitError — submission feedback
├── Effects (3):
│ ├── Fetch accounts on modal open
│ ├── Fetch clusters when selectedAccount changes
│ └── Reset all state on modal close
├── Validation: isTargetValid, isExecutionValid, isFormValid
└── Submit handler: handlerConfirmActionCreation
Proposed Solution
Split into 3 focused form sections, keeping the parent as the state orchestrator:
1. OperationSelector.tsx (~40 lines)
The toggle group for Scan / Power On / Power Off. Receives value and onChange as props.
interface OperationSelectorProps {
value: string;
onChange: (operation: string) => void;
}
2. TargetSelector.tsx (~80 lines)
Account and cluster selection. Handles fetching accounts/clusters and renders the typeahead selects. Receives the modal's isOpen state to trigger fetches.
interface TargetSelectorProps {
isOpen: boolean;
isScan: boolean;
selectedAccount: AccountResponseApi | null;
onSelectAccount: (account: AccountResponseApi | null) => void;
selectedCluster: ClusterResponseApi | null;
onSelectCluster: (cluster: ClusterResponseApi | null) => void;
}
This component owns allAccounts and allClusters state and their fetch effects, since no other component needs them.
3. ExecutionConfig.tsx (~100 lines)
Schedule checkbox, specific time vs cron radio, datetime picker, cron input with validation. Owns showSchedule, cronTouched state internally.
interface ExecutionConfigProps {
actionType: ActionTypes;
onActionTypeChange: (type: ActionTypes) => void;
scheduledDateTime: string;
onScheduledDateTimeChange: (dt: string) => void;
cronExpression: string;
onCronExpressionChange: (expr: string) => void;
}
Result in ModalCreateAction
The parent shrinks to ~120 lines handling:
- Top-level state for the values that cross section boundaries (operation, account, cluster, actionType, datetime, cron, description)
- Cross-field validation (
isFormValid)
- Submit handler
- Modal chrome (title, confirm/cancel buttons, error alert)
- Composition of the 3 sections
<Modal ...>
{submitError && <Alert ... />}
<Form>
<OperationSelector value={op} onChange={handleOperationChange} />
<TargetSelector isOpen={isOpen} isScan={isScan} ... />
<ExecutionConfig actionType={actionType} ... />
<FormGroup label="Description">...</FormGroup>
</Form>
</Modal>
Considerations
- Validation stays in the parent:
isFormValid depends on values from all 3 sections, so it cannot be pushed down.
- Reset effect stays in the parent: it resets cross-section state on modal close.
handleOperationChange resets cluster, schedule, and cron state — it stays in the parent since it spans sections.
- TargetSelector owns its own fetch effects because
allAccounts and allClusters are only needed for the typeahead dropdowns. This keeps the parent's state footprint minimal.
Files to Create
console/src/app/Actions/Scheduler/components/OperationSelector.tsx
console/src/app/Actions/Scheduler/components/TargetSelector.tsx
console/src/app/Actions/Scheduler/components/ExecutionConfig.tsx
Files to Modify
console/src/app/Actions/Scheduler/components/ModalCreateAction.tsx — remove extracted code, import new components
Problem
console/src/app/Actions/Scheduler/components/ModalCreateAction.tsxis a 345-line component that handles the entire action creation flow in a single file:AccountTypeaheadSelectwrapperClusterTypeaheadSelectwrapperAll state (11
useStatecalls) and 3useEffectcalls live at the top level. The component is hard to reason about because changes to any section require understanding the full state tree.Current State Map
Proposed Solution
Split into 3 focused form sections, keeping the parent as the state orchestrator:
1.
OperationSelector.tsx(~40 lines)The toggle group for Scan / Power On / Power Off. Receives
valueandonChangeas props.2.
TargetSelector.tsx(~80 lines)Account and cluster selection. Handles fetching accounts/clusters and renders the typeahead selects. Receives the modal's
isOpenstate to trigger fetches.This component owns
allAccountsandallClustersstate and their fetch effects, since no other component needs them.3.
ExecutionConfig.tsx(~100 lines)Schedule checkbox, specific time vs cron radio, datetime picker, cron input with validation. Owns
showSchedule,cronTouchedstate internally.Result in ModalCreateAction
The parent shrinks to ~120 lines handling:
isFormValid)Considerations
isFormValiddepends on values from all 3 sections, so it cannot be pushed down.handleOperationChangeresets cluster, schedule, and cron state — it stays in the parent since it spans sections.allAccountsandallClustersare only needed for the typeahead dropdowns. This keeps the parent's state footprint minimal.Files to Create
console/src/app/Actions/Scheduler/components/OperationSelector.tsxconsole/src/app/Actions/Scheduler/components/TargetSelector.tsxconsole/src/app/Actions/Scheduler/components/ExecutionConfig.tsxFiles to Modify
console/src/app/Actions/Scheduler/components/ModalCreateAction.tsx— remove extracted code, import new components