Skip to content

feat: migrate track chair list and edit to mui components#918

Open
tomrndom wants to merge 6 commits into
masterfrom
fix/track-chair-list-mui
Open

feat: migrate track chair list and edit to mui components#918
tomrndom wants to merge 6 commits into
masterfrom
fix/track-chair-list-mui

Conversation

@tomrndom

@tomrndom tomrndom commented May 6, 2026

Copy link
Copy Markdown

ref: https://app.clickup.com/t/86b9pc89t

image image

Signed-off-by: Tomás Castillo tcastilloboireau@gmail.com

Summary by CodeRabbit

  • New Features
    • Added a modal dialog for creating/editing track chairs, with member autocomplete, multi-track selection, and validation.
  • Refactor
    • Rebuilt the track chair list page as a hooks-based flow using a Material-UI table and dialog-driven add/edit/delete.
  • Bug Fixes
    • Improved action error handling with snackbars, ensured loading state clears consistently, and enhanced request tracking for track chair queries and CSV/export.
  • Documentation
    • Updated track chair deletion confirmation text.
  • Tests
    • Added Jest/RTL coverage for the track chair list page and the dialog’s validation, save, and disabled-state behavior.

Signed-off-by: Tomás Castillo <tcastilloboireau@gmail.com>
@coderabbitai

coderabbitai Bot commented May 6, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5371b099-e68c-4294-8d08-4ad8f912f924

📥 Commits

Reviewing files that changed from the base of the PR and between d048d69 and 5d16356.

📒 Files selected for processing (4)
  • src/pages/track_chairs/__tests__/track-chair-list-page.test.js
  • src/pages/track_chairs/components/__tests__/track-chair-dialog.test.js
  • src/pages/track_chairs/components/track-chair-dialog.js
  • src/pages/track_chairs/track-chair-list-page.js
💤 Files with no reviewable changes (1)
  • src/pages/track_chairs/tests/track-chair-list-page.test.js
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/pages/track_chairs/components/tests/track-chair-dialog.test.js
  • src/pages/track_chairs/track-chair-list-page.js
  • src/pages/track_chairs/components/track-chair-dialog.js

📝 Walkthrough

Walkthrough

Refactors Track Chairs to a hooks-based MUI page, adds a Formik-based TrackChairDialog, updates track-chair action requests/error handling (adds member.id, passes meta, uses snackbarErrorHandler and .finally()), adjusts reducer reset/perPage behavior, enhances async select prop forwarding, and tweaks an i18n delete warning string.

Changes

Track Chairs UI Modernization & Error Handling

Layer / File(s) Summary
Action creators: error handling & request meta
src/actions/track-chair-actions.js
Adds member.id to fields; getTrackChairs now sends meta { trackId, term, order, orderDir, perPage }; replaces authErrorHandler with snackbarErrorHandler; uses .finally() to ensure stopLoading() runs regardless of outcome.
Reducer: reset behavior & perPage state
src/reducers/track_chairs/track-chair-list-reducer.js
Resets reducer on SET_CURRENT_SUMMIT and LOGOUT_USER; REQUEST_TRACK_CHAIRS stores perPage; RECEIVE_TRACK_CHAIRS uses shorthand for trackChairs.
Async select prop forwarding
src/components/mui/formik-inputs/mui-formik-async-select.js
Enhances MuiFormikAsyncAutocomplete to accept and forward additional props via ...rest to underlying MUI Autocomplete.
TrackChairDialog component & tests
src/pages/track_chairs/components/track-chair-dialog.js, src/pages/track_chairs/components/__tests__/track-chair-dialog.test.js
New TrackChairDialog: Formik + Yup validation (member required, ≥1 track), async member autocomplete, multi-select tracks with removable chips, reinitialization on entity change, isSaving duplicate-submit gating. Tests verify validation, form submission, member/track selection, and isSaving state behavior.
TrackChairListPage functional refactor & MUI table
src/pages/track_chairs/track-chair-list-page.js, src/pages/track_chairs/__tests__/track-chair-list-page.test.js
Converts from class to functional hooks-based component using MUI; implements search, filter-by-track, sort, pagination handlers; dialog wiring for add/edit/delete; determines addTrackChair vs saveTrackChair based on member change detection. Tests verify dialog visibility, edit entity mapping, delete routing, and save logic.
Localization update
src/i18n/en.json
Updated track_chairs.delete_warning to "Are you sure you want to remove track chair for".

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant ListPage as TrackChairListPage
  participant Dialog as TrackChairDialog
  participant Actions as Redux Actions
  participant API as Backend API

  rect rgba(100, 150, 200, 0.5)
  Note over User,API: Initial page load
  ListPage->>Actions: getTrackChairs(meta: trackId, term, order, orderDir, perPage)
  Actions->>API: GET /track-chairs?meta...
  API-->>Actions: 200 + trackChairs (with member.id)
  Actions->>ListPage: dispatch RECEIVE_TRACK_CHAIRS
  end

  rect rgba(150, 200, 150, 0.5)
  Note over User,Dialog: Add new track chair
  User->>ListPage: click Add button
  ListPage->>Dialog: render with empty entity
  User->>Dialog: select member, track(s), Save
  Dialog->>Actions: addTrackChair(memberId, trackIds)
  Actions->>API: POST /track-chairs
  API-->>Actions: 201 + new chair
  Actions->>ListPage: dispatch success
  ListPage->>Actions: getTrackChairs (refresh)
  ListPage->>Dialog: close (dialogEntity=null)
  end

  rect rgba(200, 150, 100, 0.5)
  Note over User,Dialog: Edit existing track chair
  User->>ListPage: click edit icon
  ListPage->>Dialog: render with populated entity
  User->>Dialog: modify tracks, Save
  Dialog->>Actions: saveTrackChair(chairId, trackIds)
  Actions->>API: PUT /track-chairs/{id}
  API-->>Actions: 200 + updated chair
  ListPage->>Actions: getTrackChairs (refresh)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • martinquiroga-exo
  • santipalenque
  • smarcet

🐰 Hops through dialogs with Formik and grace,
Snackbars catch errors all over the place,
Chips are now removable, state management's tight,
From class to hooks—what a beautiful sight!
This PR makes the code-warren shine bright

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: migrating track chair list and edit functionality to MUI components, which is reflected across all modified files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/track-chair-list-mui

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/actions/track-chair-actions.js (1)

149-157: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Always clear loading state on add failures.

This path still dispatches stopLoading() only on success. If postRequest rejects, the global loading state stays active and the new dialog flow gets stuck after a failed create.

Suggested fix
     return postRequest(
       null,
       createAction(TRACK_CHAIR_ADDED),
       `${window.API_BASE_URL}/api/v1/summits/${currentSummit.id}/track-chairs`,
       { member_id: member.id, categories: trackIds },
       snackbarErrorHandler
-    )(params)(dispatch).then(() => {
+    )(params)(dispatch).finally(() => {
       dispatch(stopLoading());
     });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/actions/track-chair-actions.js` around lines 149 - 157, The
add-track-chair flow only calls dispatch(stopLoading()) in the success .then,
leaving the global loading state active on postRequest rejection; update the
returned promise chain that wraps postRequest(...)(params)(dispatch) so that
dispatch(stopLoading()) runs regardless of outcome (use .finally or a .catch
that rethrows after dispatching stopLoading()). Reference the postRequest call
that uses createAction(TRACK_CHAIR_ADDED), snackbarErrorHandler, and ensure
stopLoading is dispatched in the failure path as well.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/pages/track_chairs/track-chair-list-page.js`:
- Around line 125-131: The current handleSave treats a changed member in edit
mode as an add, causing duplicate assignments; update handleSave to perform an
explicit replace instead of add when dialogEntity?.originalMemberId !==
member?.value and id exists: either call a replaceTrackChair(member.value, id,
trackIds) helper (preferred) or sequentially call removeTrackChair(id) then
addTrackChair({ id: member.value }, trackIds), and finally clear the dialog;
alternatively, if reassignment is not allowed make the member field read-only in
edit mode (in the dialog render) so the branch to addTrackChair never runs for
edits. Ensure you reference dialogEntity.originalMemberId, handleSave,
addTrackChair, saveTrackChair and remove/replace helper names when implementing
the change.
- Around line 57-63: The early return based on currentSummit.id happens before
hooks run and can cause hook-order errors; keep the declarations (e.g., const
[dialogEntity, setDialogEntity] = useState(null)) and the useEffect hook in
place, move the render guard that checks currentSummit.id below the hooks (so
hooks always register), and change the effect to depend on currentSummit?.id
(useEffect(() => { if (currentSummit?.id) getTrackChairs(); },
[currentSummit?.id])) so getTrackChairs runs when the summit arrives.

---

Outside diff comments:
In `@src/actions/track-chair-actions.js`:
- Around line 149-157: The add-track-chair flow only calls
dispatch(stopLoading()) in the success .then, leaving the global loading state
active on postRequest rejection; update the returned promise chain that wraps
postRequest(...)(params)(dispatch) so that dispatch(stopLoading()) runs
regardless of outcome (use .finally or a .catch that rethrows after dispatching
stopLoading()). Reference the postRequest call that uses
createAction(TRACK_CHAIR_ADDED), snackbarErrorHandler, and ensure stopLoading is
dispatched in the failure path as well.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f4ec317b-b43b-40a7-8d38-7a99aa796a4b

📥 Commits

Reviewing files that changed from the base of the PR and between 8607ac5 and e7841ab.

📒 Files selected for processing (5)
  • src/actions/track-chair-actions.js
  • src/i18n/en.json
  • src/pages/track_chairs/components/track-chair-dialog.js
  • src/pages/track_chairs/track-chair-list-page.js
  • src/reducers/track_chairs/track-chair-list-reducer.js

Comment thread src/pages/track_chairs/track-chair-list-page.js Outdated
Comment thread src/pages/track_chairs/track-chair-list-page.js Outdated
Signed-off-by: Tomás Castillo <tcastilloboireau@gmail.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
src/pages/track_chairs/track-chair-list-page.js (1)

57-63: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Runtime crash: currentSummit.tracks accessed before guard.

Line 63 accesses currentSummit.tracks before the guard at line 157. If currentSummit is undefined, this crashes. Additionally, the useEffect has an empty dependency array, so getTrackChairs won't be called when the summit loads after mount.

Move chairTracks derivation and the early return guard together, and add proper dependencies to the effect:

Proposed fix
   const [dialogEntity, setDialogEntity] = useState(null);

+  const chairTracks = currentSummit?.tracks?.filter((t) => t.chair_visible) ?? [];
+
   useEffect(() => {
-    if (currentSummit) getTrackChairs();
-  }, []);
+    if (currentSummit?.id) getTrackChairs();
+  }, [currentSummit?.id]);
 
-  const chairTracks = currentSummit.tracks.filter((t) => t.chair_visible);
+  if (!currentSummit?.id) return <div />;

Then remove the duplicate guard at line 157.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/track_chairs/track-chair-list-page.js` around lines 57 - 63, Move
the derivation of chairTracks so it runs only after the early-return guard that
checks currentSummit (i.e., return/loading when !currentSummit) to avoid
accessing currentSummit.tracks when currentSummit is undefined; update the
useEffect that calls getTrackChairs to include currentSummit and getTrackChairs
in its dependency array (useEffect(() => { if (currentSummit) getTrackChairs();
}, [currentSummit, getTrackChairs])) so the fetch runs when the summit loads,
and remove the now-duplicate early-return guard later in the component;
references: chairTracks, currentSummit, getTrackChairs, useEffect.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@src/pages/track_chairs/track-chair-list-page.js`:
- Around line 57-63: Move the derivation of chairTracks so it runs only after
the early-return guard that checks currentSummit (i.e., return/loading when
!currentSummit) to avoid accessing currentSummit.tracks when currentSummit is
undefined; update the useEffect that calls getTrackChairs to include
currentSummit and getTrackChairs in its dependency array (useEffect(() => { if
(currentSummit) getTrackChairs(); }, [currentSummit, getTrackChairs])) so the
fetch runs when the summit loads, and remove the now-duplicate early-return
guard later in the component; references: chairTracks, currentSummit,
getTrackChairs, useEffect.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1b6b9930-5b7b-4fbd-8a77-4a9210e5efbe

📥 Commits

Reviewing files that changed from the base of the PR and between e7841ab and 0cb4096.

📒 Files selected for processing (1)
  • src/pages/track_chairs/track-chair-list-page.js

Signed-off-by: Tomás Castillo <tcastilloboireau@gmail.com>
@tomrndom tomrndom requested a review from smarcet May 6, 2026 21:47
Signed-off-by: Tomás Castillo <tcastilloboireau@gmail.com>
setIsSaving(true);
const newMember = dialogEntity?.originalMemberId !== member?.value;
const action =
!id || newMember

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When editing a track chair and the user selects a different member, this branch calls addTrackChair (POST — new record) but never deletes the original. After save, both records appear in the list: the old one with member A and the new one with member B, both holding the same track assignments.

saveTrackChair only sends { categories: trackIds } — the PUT endpoint does not accept member_id — so changing the member via PUT is not supported. Two options:

  1. Call deleteTrackChair(id) before addTrackChair when newMember is true.
  2. Disable the member field in edit mode to match the original form behavior (which never allowed changing the member).

exportTrackChairs
}) => {
const [dialogEntity, setDialogEntity] = useState(null);
const [isSaving, setIsSaving] = useState(false);

@smarcet smarcet Jun 18, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isSaving belongs inside TrackChairDialog, not here. This is the same deviation tracked in https://app.clickup.com/t/86bag2zk7 — now introduced in a new page.

The pattern (from summit-admin-popup-dialog-pattern.md and the reference impl in media-file-type-list-page.js):

Parent — returns the promise, triggers list reload, never touches isSaving:

const handleSave = ({ id, member, trackIds }) => {
  const newMember = dialogEntity?.originalMemberId !== member?.value;
  return (!id || newMember
    ? addTrackChair({ id: member.value }, trackIds)
    : saveTrackChair(id, trackIds)
  ).then(() => getTrackChairs(trackId, term, DEFAULT_CURRENT_PAGE, perPage, order, orderDir));
};

Dialog — owns isSaving, closes itself on success, stays open on error:

const [isSaving, setIsSaving] = useState(false);

const handleOnSave = (values) => {
  if (isSaving) return;
  setIsSaving(true);
  Promise.resolve(onSave(values))
    .then(() => onClose())
    .catch(() => {})
    .finally(() => setIsSaving(false));
};

Also remove the isSaving prop from <TrackChairDialog> — the dialog no longer receives it from outside.

>
<Button
variant="outlined"
onClick={() => exportTrackChairs(trackChairs)}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exportTrackChairs is defined as () => async (dispatch, getState) => ... — no parameters. The trackChairs argument passed here is silently discarded; the action reads its own data from trackChairListState via getState(). Change to () => exportTrackChairs().

onSave: PropTypes.func.isRequired
};

export default TrackChairDialog;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No tests for this new component. Critical paths to cover:

  • Validation fires when member or trackIds is empty (submit should be blocked)
  • onSave receives { id, member, trackIds } on valid submit
  • Save button disabled while isSaving={true}
  • Close button and Escape key blocked while isSaving={true}

The handleSave branching in the list page (addTrackChair vs saveTrackChair, and the missing delete-then-add path when the member changes) is also untested.

@smarcet smarcet left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tomrndom please review comments

Signed-off-by: Tomás Castillo <tcastilloboireau@gmail.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/mui/formik-inputs/mui-formik-async-select.js`:
- Line 124: The spread operator `{...rest}` is positioned after critical
internal props in the component's prop list, allowing consumer-provided props to
override essential handlers like `onChange`, `value`, `loading`, and `multiple`,
which breaks Formik form synchronization. Move the `{...rest}` spread to appear
before all critical internal props (such as `onChange`, `value`, `loading`,
`multiple`, `onBlur`, etc.) so that the internal props always take precedence
and cannot be overridden by consumer input.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 156c349d-b0e9-4cba-b712-1f5bd86f29fe

📥 Commits

Reviewing files that changed from the base of the PR and between d6f6226 and d048d69.

📒 Files selected for processing (5)
  • src/components/mui/formik-inputs/mui-formik-async-select.js
  • src/pages/track_chairs/__tests__/track-chair-list-page.test.js
  • src/pages/track_chairs/components/__tests__/track-chair-dialog.test.js
  • src/pages/track_chairs/components/track-chair-dialog.js
  • src/pages/track_chairs/track-chair-list-page.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/pages/track_chairs/components/track-chair-dialog.js
  • src/pages/track_chairs/track-chair-list-page.js

{option.label}
</li>
)}
{...rest}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Risk: {...rest} can override critical internal props.

Spreading {...rest} after all other props means consumer-provided props like onChange, value, loading, or multiple can override the component's internal Formik-integrated handlers, breaking form synchronization silently.

Consider one of these safer patterns:

  1. Spread {...rest} before critical props so internal props take precedence.
  2. Explicitly allow only safe passthrough props (e.g., disabled, sx, className, testId).
🛡️ Recommended fix: spread rest before critical props
     />
   )}
-  {...rest}
+  disabled={disabled}
+  sx={sx}
+  className={className}
 />

Or if you need full flexibility, spread before:

   return (
     <Autocomplete
+      {...rest}
       options={options}
       value={value}
       onChange={handleChange}
       loading={loading}
       multiple={isMulti}
       ...
-      {...rest}
     />
   );

Note: If spreading before, document which props consumers can safely override.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/mui/formik-inputs/mui-formik-async-select.js` at line 124, The
spread operator `{...rest}` is positioned after critical internal props in the
component's prop list, allowing consumer-provided props to override essential
handlers like `onChange`, `value`, `loading`, and `multiple`, which breaks
Formik form synchronization. Move the `{...rest}` spread to appear before all
critical internal props (such as `onChange`, `value`, `loading`, `multiple`,
`onBlur`, etc.) so that the internal props always take precedence and cannot be
overridden by consumer input.

Signed-off-by: Tomás Castillo <tcastilloboireau@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants