Skip to content

refactor(console): decompose ModalCreateAction into focused subcomponents #277

Description

@r2dedios

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:

  1. Operation selection (lines 66-74, 208-228): Scan / Power On / Power Off toggle group
  2. Account selection (lines 76-97): fetch accounts on open, AccountTypeaheadSelect wrapper
  3. Cluster selection (lines 99-126): fetch clusters when account changes, ClusterTypeaheadSelect wrapper
  4. Execution configuration (lines 128-141, 251-328): instant vs scheduled vs cron, datetime picker, cron input with validation
  5. Form validation (lines 49-64): cross-field validation combining operation, target, and execution state
  6. 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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions