diff --git a/AGENTS.md b/AGENTS.md index a3ea19ff71..6ac19e7d34 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -71,7 +71,7 @@ This codebase (Rails 8.1) | Directory | Purpose | |---|---| | `app/frontend/entrypoints/` | Vite entry points (application.js, application.css) | -| `app/frontend/javascript/controllers/` | Stimulus controllers (75) | +| `app/frontend/javascript/controllers/` | Stimulus controllers (76) | | `app/frontend/javascript/rhino/` | Rich text editor customizations (mentions, grid) | | `app/frontend/stylesheets/` | Tailwind CSS and component styles | @@ -303,6 +303,7 @@ end - `confirm_email` — Email confirmation UI - `dirty_form` — Unsaved changes detection - `dismiss` — Dismissable elements +- `details_row_sync` — Keeps a row of `
` cards in lockstep: opening/closing one mirrors onto the rest of the row (event dashboard breakdown cards) - `dropdown` — Dropdown menus with keyboard/click-outside handling - `edit_toggle` — Inline view/edit toggle for the comments and communications boxes (configurable view/edit CSS classes) - `event_staff_bio` — Loads a selected person's read-only profile bio (with edit link) alongside the editable event-specific bio on the staff form diff --git a/app/frontend/javascript/controllers/details_row_sync_controller.js b/app/frontend/javascript/controllers/details_row_sync_controller.js new file mode 100644 index 0000000000..ca227e9a73 --- /dev/null +++ b/app/frontend/javascript/controllers/details_row_sync_controller.js @@ -0,0 +1,19 @@ +import { Controller } from "@hotwired/stimulus" + +// Connects to data-controller="details-row-sync" +// Keeps a row of
cards in lockstep: opening or closing one mirrors its +// open/closed state onto the rest of the row. Each card fires a native `toggle` +// event (which does not bubble, so nested
inside a card are ignored); +// we copy the toggled card's `open` onto its siblings. Mirroring only sets cards +// whose state actually differs, so the cascade of toggle events it triggers +// converges immediately instead of looping. +export default class extends Controller { + static targets = ["card"] + + sync(event) { + const open = event.target.open + this.cardTargets.forEach(card => { + if (card.open !== open) card.open = open + }) + } +} diff --git a/app/frontend/javascript/controllers/index.js b/app/frontend/javascript/controllers/index.js index 346eda3b17..9442e89796 100644 --- a/app/frontend/javascript/controllers/index.js +++ b/app/frontend/javascript/controllers/index.js @@ -75,6 +75,9 @@ application.register("expandable-card", ExpandableCardController) import ExpandableCardsController from "./expandable_cards_controller" application.register("expandable-cards", ExpandableCardsController) +import DetailsRowSyncController from "./details_row_sync_controller" +application.register("details-row-sync", DetailsRowSyncController) + import DropdownController from "./dropdown_controller" application.register("dropdown", DropdownController) diff --git a/app/views/events/dashboard.html.erb b/app/views/events/dashboard.html.erb index 9a979df633..ee9730e391 100644 --- a/app/views/events/dashboard.html.erb +++ b/app/views/events/dashboard.html.erb @@ -129,9 +129,9 @@ <%# The three addends: Registration fees + Scholarships + Continuing education fees. Each owns paid/completed + outstanding sub-rows that expand to the registrants behind the figure. %> -
+
<%# Payments = Paid + Outstanding %> -
+
@@ -171,7 +171,7 @@
<%# Scholarships = Completed + Outstanding %> -
+
@@ -203,7 +203,7 @@
<%# Continuing education fees = Paid + Outstanding %> -
+
@@ -244,8 +244,8 @@ <% end %> <%# Headcount cards — each expands to reveal its full list %> -
-
+
+

@@ -276,7 +276,7 @@

-
+

@@ -314,7 +314,7 @@

-
+

@@ -346,7 +346,7 @@

-
+

diff --git a/spec/system/event_dashboard_details_row_sync_spec.rb b/spec/system/event_dashboard_details_row_sync_spec.rb new file mode 100644 index 0000000000..97537ccc06 --- /dev/null +++ b/spec/system/event_dashboard_details_row_sync_spec.rb @@ -0,0 +1,30 @@ +require "rails_helper" + +# The dashboard groups its breakdown cards into rows of native
. Opening +# or closing one card should mirror onto the rest of that row (details-row-sync +# controller). +RSpec.describe "Event dashboard details row sync", type: :system, js: true do + let(:admin) { create(:user, :admin) } + let(:event) { create(:event, :published, cost: 25) } + + before { sign_in admin } + + it "opens and closes every card in a row together" do + visit dashboard_event_path(event) + + # The headcount row (Registrants / Organizations / Sectors / States) always + # renders regardless of the event's data. Grab it via a card unique to it. + sectors_card = find("details.group\\/card", text: "Sectors") + row = sectors_card.find(:xpath, "..") + card_count = row.all("details.group\\/card", minimum: 2).size + + # All start collapsed. + expect(row).to have_no_css("details.group\\/card[open]") + + sectors_card.find("summary").click + expect(row).to have_css("details.group\\/card[open]", count: card_count, wait: 5) + + sectors_card.find("summary").click + expect(row).to have_no_css("details.group\\/card[open]", wait: 5) + end +end