Skip to content

New Case Contacts Table: row actions menu#6834

Open
cliftonmcintosh wants to merge 13 commits intorubyforgood:mainfrom
cliftonmcintosh:feature/6500-case-contacts-new-design-row-actions-menu
Open

New Case Contacts Table: row actions menu#6834
cliftonmcintosh wants to merge 13 commits intorubyforgood:mainfrom
cliftonmcintosh:feature/6500-case-contacts-new-design-row-actions-menu

Conversation

@cliftonmcintosh
Copy link
Copy Markdown
Collaborator

@cliftonmcintosh cliftonmcintosh commented Apr 9, 2026

What github issue is this PR for, if any?

Resolves #6500

What changed, and why?

Implements the ellipsis (⋮) action menu for each row in the new case contacts table at /case_contacts/new_design. The menu contains three items: Edit, Delete, and Set Reminder (or Resolve Reminder if a pending followup already exists).

app/datatables/case_contact_datatable.rb

  • Added current_user parameter to the initializer so the datatable can compute per-row permissions
  • Added per-row fields to the JSON response: can_edit, can_destroy, edit_path, and followup_id
    • can_edit and can_destroy are derived from CaseContactPolicy so the UI reflects actual Pundit permissions
    • followup_id is the id of any open (requested) followup, used to toggle between Set Reminder and Resolve Reminder
  • Updated includes to add :followups and :creator to avoid N+1 queries

app/controllers/case_contacts/case_contacts_new_design_controller.rb

  • Passed current_user to CaseContactDatatable.new

app/controllers/case_contacts_controller.rb

  • Added respond_to to destroy so jQuery AJAX receives 204 No Content instead of an HTML redirect that would be silently followed

app/controllers/case_contacts/followups_controller.rb

  • Added respond_to to resolve for the same reason

app/javascript/src/dashboard.js

  • Imported SweetAlert2
  • Replaced the placeholder ellipsis icon in the last column with a fully-functional Bootstrap dropdown menu
    • Edit links to edit_path with data-turbo="false"; rendered as a disabled <span> when can_edit is false
    • Delete triggers a SweetAlert2 confirmation dialog before sending a DELETE request via AJAX; rendered as a disabled <button> when can_destroy is false
    • Set Reminder opens a SweetAlert2 dialog for an optional note, then POSTs to the followups endpoint; shown when no open followup exists
    • Resolve Reminder sends a PATCH to the followup resolve endpoint; shown when an open followup exists
    • All three actions reload the DataTable in place on success so the row reflects the new state
  • All AJAX requests include the CSRF token from the page's <meta> tag
  • The dropdown toggle button includes aria-label, aria-haspopup, and aria-expanded attributes; the icon is marked aria-hidden="true"

How is this tested? (please write rspec and jest tests!) 💖💪

Note: if you see a flake in your test build in github actions, please post in slack #casa "Flaky test: " :) 💪
Note: We love capybara tests! If you are writing both haml/js and ruby, please try to test your work with tests at every level including system tests like https://github.com/rubyforgood/casa/tree/main/spec/system

Datatable spec — updated spec/datatables/case_contact_datatable_spec.rb:

  • can_edit is "true" for admin, "false" for a different volunteer's contact
  • can_destroy reflects policy (admin can destroy active contacts; volunteers cannot)
  • edit_path is the correct Rails edit path for the contact
  • followup_id is empty when no requested followup exists
  • followup_id contains the followup id when a requested followup exists

Request specs — updated spec/requests/case_contacts/case_contacts_new_design_spec.rb:

  • Datatable response includes can_edit, can_destroy, edit_path, and followup_id for each row
  • Admin sees can_edit: "true" and can_destroy: "true"
  • Volunteer sees can_edit: "true" for their own contact and can_destroy: "false" for active contacts
  • Volunteer sees can_destroy: "true" for their own draft contacts

Jest — updated app/javascript/__tests__/dashboard.test.js:

Ellipsis column rendering:

  • Toggle button has the correct aria-label containing the contact date
  • Icon is aria-hidden="true"
  • Edit link is rendered when can_edit is "true"; rendered as a disabled <span> when "false"
  • Delete button is rendered when can_destroy is "true"; rendered as a disabled <button> with aria-disabled="true" when "false"
  • Set Reminder is shown when followup_id is empty
  • Resolve Reminder is shown (with data-followup-id) when followup_id is present

Click handlers:

  • Delete: shows a SweetAlert2 confirmation; sends DELETE with CSRF header when confirmed; does not send request when cancelled; reloads DataTable on success
  • Set Reminder: fires SweetAlert2; POSTs with empty params when confirmed without a note; POSTs with { note } when confirmed with a note; does not POST when cancelled; reloads DataTable on success
  • Resolve Reminder: sends PATCH to /followups/:id/resolve with CSRF header; reloads DataTable on success

System specs — updated spec/system/case_contacts/case_contacts_new_design_spec.rb:

Action menu visibility:

  • Ellipsis button opens the dropdown
  • Edit, Delete, Set Reminder, and Resolve Reminder items are present
  • Set Reminder is shown when no followup exists; Resolve Reminder is shown when one does
  • Clicking outside closes the dropdown

Edit action:

  • Navigates to the edit form

Delete action:

  • Confirms then removes the row from the table
  • Cancels and leaves the row in place

Set Reminder action:

  • Confirms then shows Resolve Reminder in the menu (table reloads)
  • Cancels without creating a followup

Resolve Reminder action:

  • Resolves the followup and shows Set Reminder in the menu afterwards
  • Marks the followup record as resolved in the database

Permission states:

  • Delete is rendered as disabled for a volunteer's active contact
  • Delete is rendered as enabled for a volunteer's draft contact

Screen recordings

Edit case contact
edit.contact.mov
Delete case contact
delete.case.contact.mov
Delete disabled for volunteer

A volunteer can only delete a draft case contact. This screen recording shows the Delete disabled for non-drafts and the Delete working for drafts.

volunteer.can.only.delete.drafts.mov
Set reminder
set.reminder.mov
Resolve reminder
resolve.reminder.mov

AI Disclosure

This PR was implemented with the assistance of Claude Code (Anthropic, claude-sonnet-4-6). The AI was used to:

  • Analyze the codebase and existing patterns (Pundit policies, SweetAlert2 usage, CSRF handling) to inform the implementation approach
  • Draft and iterate on code changes across the datatable, controllers, JavaScript, and test files
  • Write and run RSpec request/datatable specs and Jest tests, including debugging test failures caused by DataTables DOM re-rendering behavior
  • Investigate and resolve a CSRF/redirect issue with jQuery following HTML redirects from Rails actions that didn't yet have JSON responses

All generated code was reviewed and committed by the author.

Action menu clipped near bottom of table

When a row is near the bottom of the table, the dropdown action menu may be partially obscured by the table footer or page content below it.

The root cause seems to be overflow-x: hidden on the <body> element, which clips position: absolute elements (including Bootstrap dropdowns) that extend beyond the body's paint area. I'm worried that fixing this globally risks unintended layout side effects elsewhere, so it is left as is.

Menu is hidden by table footer
menu.hidden.by.footer.mov

@github-actions github-actions bot added javascript for use by Github Labeler to mark pull requests that update Javascript code ruby Pull requests that update Ruby code Tests! 🎉💖👏 labels Apr 9, 2026
@cliftonmcintosh cliftonmcintosh force-pushed the feature/6500-case-contacts-new-design-row-actions-menu branch from 36e1e71 to 7a11bdd Compare April 9, 2026 17:36
@cliftonmcintosh cliftonmcintosh self-assigned this Apr 9, 2026
cliftonmcintosh and others added 12 commits April 9, 2026 15:40
Co-Authored-By: claude-sonnet-4-6 <noreply@anthropic.com>
Each row in the new case contacts datatable now has a Bootstrap dropdown
menu with Edit, Delete, and Set/Resolve Reminder actions. Items are shown
or hidden based on per-row Pundit permissions already returned by the
datatable JSON. Delete and Resolve Reminder use AJAX so the table reloads
in place. Set Reminder opens a SweetAlert2 dialog for an optional note.
Backend controllers now respond to JSON for destroy and followup resolve
so jQuery does not follow the HTML redirect.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The delete action in the new case contacts table now shows a
confirmation dialog before sending the DELETE request. Cancelling the
dialog aborts the request. Tests updated to reflect async flow.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Edit and Delete are now rendered as disabled dropdown items when the
current user lacks permission, rather than being omitted from the menu.
Disabled items retain aria-disabled="true" for screen reader accessibility.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@cliftonmcintosh cliftonmcintosh force-pushed the feature/6500-case-contacts-new-design-row-actions-menu branch from b50076e to e59e9ee Compare April 9, 2026 20:41
@cliftonmcintosh cliftonmcintosh changed the title Case contacts new design row actions menu New Case Contacts Table: row actions menu Apr 9, 2026
@cliftonmcintosh cliftonmcintosh force-pushed the feature/6500-case-contacts-new-design-row-actions-menu branch 3 times, most recently from 1600b95 to 92abe1e Compare April 9, 2026 21:59
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@cliftonmcintosh cliftonmcintosh force-pushed the feature/6500-case-contacts-new-design-row-actions-menu branch from 92abe1e to c4370f3 Compare April 9, 2026 22:38
@cliftonmcintosh cliftonmcintosh marked this pull request as ready for review April 9, 2026 23:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

javascript for use by Github Labeler to mark pull requests that update Javascript code ruby Pull requests that update Ruby code Tests! 🎉💖👏

Projects

None yet

Development

Successfully merging this pull request may close these issues.

New Case Contact Table: implement right-hand side menu for each row

1 participant