Skip to content

Add CE status column and filter to registrants index#1833

Merged
maebeale merged 20 commits into
mainfrom
maebeale/registrant-ce-status-filter
Jun 29, 2026
Merged

Add CE status column and filter to registrants index#1833
maebeale merged 20 commits into
mainfrom
maebeale/registrant-ce-status-filter

Conversation

@maebeale

@maebeale maebeale commented Jun 22, 2026

Copy link
Copy Markdown
Collaborator

🤖 PR, suggested 👤 review level: 🔬 Inspect — substantive view/JS logic across the registrants roster plus a shared helper and the public-registration eyebrow

What is the goal of this PR and why is this important?

  • Give admins an at-a-glance, triage-friendly registrants roster: see CE credit standing, keep the table dense and readable, and navigate without losing their place.
  • CE only applies to events that grant CE credit, so CE surfaces stay hidden elsewhere.

How did you approach the change?

  • CE status: new EventRegistration.ce_status scope + shared ce_status_label; a "CE status" column (scholarship-style pill linking to the registration's CE section) and dropdown filter, plus onboarding columns, bulk-reminder filter, and both CSV exports — all gated on Event#ce_eligible?.
  • Column toggles: generalized the column-toggle Stimulus controller to multiple named groups. CE on by default, Attendance off by default, User confirmation off — each with its own switch.
  • Layout/density: CE before Scholarship; Date registered moved to the far right after Edit; filters wrapped in a card; compact registrant profile button (small avatar/type, uniform width); leading chip icons removed; column widths/truncation retuned.
  • Navigation: viewing a submission from the roster stays in the same tab and its eyebrow returns to that registrant's row (anchored + highlighted), instead of the public ticket.

UI Testing Checklist

  • CE-eligible event: CE column shows by default, toggle hides it; Attendance hidden until toggled; CE filter + CSV column present.
  • Non-CE event: no CE anywhere (roster, onboarding, bulk reminders, CSVs).
  • View-submission from roster: same tab, "Back to registrants" returns to the highlighted row.

Anything else to add?

when "requested" then where(ce_credit_requested: true)
when "license_not_provided" then where(ce_credit_requested: true).where(ce_license_number: [ nil, "" ])
when "hours_not_provided" then where(ce_credit_requested: true).where("COALESCE(ce_hours_requested, 0) <= 0")
when "paid" then where(ce_credit_requested: true).merge(paid_in_full)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

🤖 From Claude: "paid" intentionally has no CE-specific payment record yet, so it falls back to the registrant being paid in full — kept in sync with ReminderRecipientFilter#matches_ce_status?.

<% extra_cols = (payment_col ? 1 : 0) + (ce_col ? 1 : 0) %>
<% ce_index = payment_col ? 4 : 3 %>
<% date_index = 4 + extra_cols %>
<% attendance_index = 5 + extra_cols %>

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

🤖 From Claude: Sort-column indices are recomputed from extra_cols so the Date/Attendance headers stay aligned whether or not the optional Payment and CE columns are present.

Comment thread app/models/event.rb Outdated
# True when at least one registrant for this event requested CE credit. Drives
# the CE status column and filter on the registrants index — there's nothing
# to show or filter when no one has asked for CE.
def any_ce_credit_requests?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

🤖 From Claude: Column/filter visibility is gated on any registrant having requested CE, so non-CE events stay uncluttered. Easy to switch to a registration-form-field check if we'd rather surface it before anyone answers.

Comment thread app/models/event.rb Outdated
# every CE surface (registrants index, onboarding matrix, bulk reminders) and
# their CSV exports, so events that don't offer CE never show empty CE
# columns/filters even if a stray registration carries CE data.
def offers_ce?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

🤖 From Claude: Single gate for every CE surface (index column/filter, onboarding matrix + CSV, bulk-reminder filter, registrants CSV): the event offers CE only when its registration form carries the seeded ce_credit_interest field — not when a stray registration happens to hold CE data.

static targets = ["toggle", "track", "knob"]
static values = { group: String }

toggle() {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

🤖 From Claude: Generalized to multiple independent switches keyed by group value, so the same controller drives both "User confirmation" and "CE status". Columns live under a shared [data-column-toggle-root] ancestor and are matched by data-column-toggle-col="<group>".

@maebeale maebeale changed the title Add CE status column and filter to registrants index WAIT: Add CE status column and filter to registrants index Jun 22, 2026
maebeale and others added 3 commits June 29, 2026 08:35
Admins need to see and triage registrants' continuing-education credit
standing at a glance — who requested CE, who is missing a license number
or hours, and who has paid — without opening each registration. Surfaces
this as a column and dropdown filter that only appear once an event has
CE requests, so non-CE events stay uncluttered, and mirrors the column in
the CSV export for parity.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CE only matters for events whose registration form actually asks the
magic CE-interest question, so empty CE columns/filters were noise on
every other event. Gate all CE UI — registrants index, onboarding matrix,
bulk-reminder filter, and both CSV exports — on Event#offers_ce? (the form
carries the ce_credit_interest field) instead of "any registrant happened
to have CE data". On the registrants index the CE status column now sits
behind its own slide toggle (defaulting off) alongside "User confirmation",
generalizing the column-toggle controller to support multiple named groups.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Main (#1916) established Event#ce_eligible? (ce_hours_offered > 0) as the
single source of truth for whether an event grants CE credit. Drop the
branch's own offers_ce? (which checked the registration form for the
ce_credit_interest field) and gate every CE surface on ce_eligible? so the
two can't drift.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@maebeale maebeale force-pushed the maebeale/registrant-ce-status-filter branch from fd17321 to bea6023 Compare June 29, 2026 12:45
maebeale and others added 17 commits June 29, 2026 09:11
- Default the CE status column on (toggle on) when the event is ce_eligible;
  add per-column toggles (CE, Attendance off-by-default, User confirmation)
  by generalizing column-toggle to named groups.
- Reorder columns: CE before Scholarship; Date registered moved to the far
  right after Edit; CE filter dropdown before Scholarship; filters wrapped
  in a card.
- Reclaim width: compact registrant profile button (teeny avatar, smaller
  type), drop leading chip icons, retune column widths/truncation.
- CE cell mirrors the scholarship pill and links to the registration edit
  (CE section); not-requested shows a "Create" chip.
- Viewing a submission from the roster now navigates in the same tab and the
  eyebrow returns to that registrant's row (anchored + highlighted).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…lish

- Add Organization (on) and Scholarship (on when the event charges a fee)
  column toggles; Scholarship column hidden by default on free events.
- Order toggles: Organization, CE, Scholarship, Attendance, User confirmation.
- Rename the CE toggle and column header to "CE".
- Registration-form icon: blue outline (fa-regular), shown only when a form was
  submitted; reserve a fixed slot otherwise so comment/warning icons stay
  aligned. Comments icon switched to outline.
- Person column a uniform w-60 (names truncate at 30).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Requested → orange; once a license is on file show the CE amount due in
orange; paid shows blue Recipient; gray Create when CE wasn't requested.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- CE cell now walks the ContinuingEducationRegistration lifecycle: Requested →
  No license # → amount due → Recipient (paid); eager-load CE records.
- Comment icon: orange-filled when any comment is flagged, otherwise the new
  `comments` theme lilac; clickable to the registration's comments section.
- Add a `comments` DomainTheme color; reuse it on the registration edit
  comments card. Make the shout-out field a textarea.
- New filters: Comments (None/Present/Flagged) and Organization
  (Pending/Linked), with scopes. Filters restructured into two rows, ordered
  to match the columns (CE, Scholarship, Payment), wider keyword box, grey
  Clear button.
- Reg-edit: org card narrower (even thirds) so scholarship/CE are wider.
- Every "Back to registrants" eyebrow (edit, link-org, public submission,
  scholarships, allocations, person profile) now anchors + highlights the row.
- Index row highlight recolored amber → yellow.
- AI files: prefer decorators over helpers for model-specific presentation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ters

Both the registrants roster and the bulk-reminder recipient filters now render
their dropdowns through one events/filter_select partial (each page keeps its
own text inputs, dropdown set, and options). DRYs the select markup without
changing any param names, options, or filtering behavior.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add a User-account filter (None/Invited/Has access/No access) to the
  registrants roster via an account_status scope (guards NULL person_ids so
  NOT IN works); shares the filter_select partial with the reminder page.
- Align the reminder filter layout to the roster: inputs + dropdowns are flex
  rows of w-48 controls that wrap (so all inputs fit a row); grey Clear button.
- Move the grey-placeholder behavior into the shared filter_select partial so
  both pages get it.
- Narrow the State dropdown so Clear fits on the row.
- Darken the index row highlight ring (yellow-400) so it reads yellow.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…t=green

The row-highlight ring rendered black because ring-<color> utilities were never
in the Tailwind @source safelist (only bg/border/text were), so the class fell
back to currentColor. Add a ring- safelist line; the highlight now builds as a
real (dark yellow) ring. Also recolor the CE chip to mirror the registration
flow: Filed → blue, Recipient (paid) → green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Keep it a textarea (multi-line on demand) but default to one row.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…r dropdowns

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…amped)

The arbitrary min-w-[16rem] never compiled; min-w-0 lets the keyword flex and
shrink so the dropdowns share one horizontal row on desktop.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Render the same dropdown filter set (attendance, payment, CE, scholarship,
organization, comments, user account, state, county) on the bulk-reminder
recipients page, sharing the events/filter_select partial. ReminderRecipientFilter
now keeps its free-text inputs in memory but delegates the dropdowns to the same
EventRegistration scopes the roster uses, so param names, options, and semantics
stay identical (and filters can carry between the two pages). preview_reminder
builds @dashboard for the state/county options.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@maebeale maebeale merged commit f6bf51d into main Jun 29, 2026
3 checks passed
@maebeale maebeale deleted the maebeale/registrant-ce-status-filter branch June 29, 2026 15:13
@maebeale maebeale changed the title WAIT: Add CE status column and filter to registrants index Add CE status column and filter to registrants index Jun 29, 2026
maebeale added a commit that referenced this pull request Jun 29, 2026
…umns

Completes the continuing-education cutover started by the foundation models
(#1916). Reroutes registration intake, the public callout, and every read
site (callouts, onboarding, reminders, CSV) off the flat
EventRegistration#ce_* columns and onto ContinuingEducationRegistration /
ProfessionalLicense, then drops the ce_credit_requested / ce_hours_requested /
ce_license_number columns.

Reconciled with #1833 (registrants-index CE column + filter), which merged to
main while this branch was in flight and built the same scaffolding on the
old columns:

- Kept #1833's scaffolding (CE column, dropdown filter, column toggles,
  layout, onboarding columns, bulk-reminder filter, CSV columns, eyebrow nav)
  and repointed every CE data read to the new models.
- EventRegistration.ce_status is now a derived scope over the CE registrations
  (needs_license / requested / paid / issued / not_issued), replacing the old
  ce_credit_requested-based buckets; the roster + reminder dropdowns follow it.
- EventRegistration#ce_status_label and the aggregators (ce_requested?,
  ce_hours_total, ce_amount_owed_cents, ce_license_provided?, ce_paid_in_full?)
  read the new models; the registrants column gates on a CE record existing
  rather than the dropped boolean (the "requested, no record yet" state is gone
  now that intake creates the record).
- ReminderRecipientFilter's CE matching flows through the shared ce_status
  scope (its per-record matchers were redundant with main's scope approach).

Migration timestamped to run after the foundation migrations now in main.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
maebeale added a commit that referenced this pull request Jun 29, 2026
…umns

Completes the continuing-education cutover started by the foundation models
(#1916). Reroutes registration intake, the public callout, and every read
site (callouts, onboarding, reminders, CSV) off the flat
EventRegistration#ce_* columns and onto ContinuingEducationRegistration /
ProfessionalLicense, then drops the ce_credit_requested / ce_hours_requested /
ce_license_number columns.

Reconciled with #1833 (registrants-index CE column + filter), which merged to
main while this branch was in flight and built the same scaffolding on the
old columns:

- Kept #1833's scaffolding (CE column, dropdown filter, column toggles,
  layout, onboarding columns, bulk-reminder filter, CSV columns, eyebrow nav)
  and repointed every CE data read to the new models.
- EventRegistration.ce_status is now a derived scope over the CE registrations
  (needs_license / requested / paid / issued / not_issued), replacing the old
  ce_credit_requested-based buckets; the roster + reminder dropdowns follow it.
- EventRegistration#ce_status_label and the aggregators (ce_requested?,
  ce_hours_total, ce_amount_owed_cents, ce_license_provided?, ce_paid_in_full?)
  read the new models; the registrants column gates on a CE record existing
  rather than the dropped boolean (the "requested, no record yet" state is gone
  now that intake creates the record).
- ReminderRecipientFilter's CE matching flows through the shared ce_status
  scope (its per-record matchers were redundant with main's scope approach).

Migration timestamped to run after the foundation migrations now in main.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
maebeale added a commit that referenced this pull request Jun 30, 2026
…umns

Completes the continuing-education cutover started by the foundation models
(#1916). Reroutes registration intake, the public callout, and every read
site (callouts, onboarding, reminders, CSV) off the flat
EventRegistration#ce_* columns and onto ContinuingEducationRegistration /
ProfessionalLicense, then drops the ce_credit_requested / ce_hours_requested /
ce_license_number columns.

Reconciled with #1833 (registrants-index CE column + filter), which merged to
main while this branch was in flight and built the same scaffolding on the
old columns:

- Kept #1833's scaffolding (CE column, dropdown filter, column toggles,
  layout, onboarding columns, bulk-reminder filter, CSV columns, eyebrow nav)
  and repointed every CE data read to the new models.
- EventRegistration.ce_status is now a derived scope over the CE registrations
  (needs_license / requested / paid / issued / not_issued), replacing the old
  ce_credit_requested-based buckets; the roster + reminder dropdowns follow it.
- EventRegistration#ce_status_label and the aggregators (ce_requested?,
  ce_hours_total, ce_amount_owed_cents, ce_license_provided?, ce_paid_in_full?)
  read the new models; the registrants column gates on a CE record existing
  rather than the dropped boolean (the "requested, no record yet" state is gone
  now that intake creates the record).
- ReminderRecipientFilter's CE matching flows through the shared ce_status
  scope (its per-record matchers were redundant with main's scope approach).

Migration timestamped to run after the foundation migrations now in main.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
maebeale added a commit that referenced this pull request Jun 30, 2026
…umns

Completes the continuing-education cutover started by the foundation models
(#1916). Reroutes registration intake, the public callout, and every read
site (callouts, onboarding, reminders, CSV) off the flat
EventRegistration#ce_* columns and onto ContinuingEducationRegistration /
ProfessionalLicense, then drops the ce_credit_requested / ce_hours_requested /
ce_license_number columns.

Reconciled with #1833 (registrants-index CE column + filter), which merged to
main while this branch was in flight and built the same scaffolding on the
old columns:

- Kept #1833's scaffolding (CE column, dropdown filter, column toggles,
  layout, onboarding columns, bulk-reminder filter, CSV columns, eyebrow nav)
  and repointed every CE data read to the new models.
- EventRegistration.ce_status is now a derived scope over the CE registrations
  (needs_license / requested / paid / issued / not_issued), replacing the old
  ce_credit_requested-based buckets; the roster + reminder dropdowns follow it.
- EventRegistration#ce_status_label and the aggregators (ce_requested?,
  ce_hours_total, ce_amount_owed_cents, ce_license_provided?, ce_paid_in_full?)
  read the new models; the registrants column gates on a CE record existing
  rather than the dropped boolean (the "requested, no record yet" state is gone
  now that intake creates the record).
- ReminderRecipientFilter's CE matching flows through the shared ce_status
  scope (its per-record matchers were redundant with main's scope approach).

Migration timestamped to run after the foundation migrations now in main.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.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.

1 participant