CE sign-in/out tracking + attendance report - #2075
Open
maebeale wants to merge 26 commits into
Open
Conversation
maebeale
force-pushed
the
maebeale/ce-attendance-report
branch
from
August 4, 2026 03:30
d85eec8 to
2e34c52
Compare
EventAttendanceReport groups a training's sign-in/out entries by day then registrant, with per-day and grand-total minutes — the in-portal CE hour sign-in sheet. `?ce=true` scopes to CE registrants and shows license number and awarded hours; the generic view covers anyone who logged time. Reached at attendance_event_path (dashboard-level auth) and linked from CE-eligible events on the participation report. Also shows a "X of Y signed in" nudge for chasing sign-ins during the training. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When attendance time has been tracked for a CE registrant, the certificate now also requires the logged minutes to cover ~90% of the awarded contact hours — you can't certify hours the sign-in sheet doesn't support. Events that never tracked time (no entries) are unaffected: day-level attendance alone still governs, so this never retroactively blocks existing certificates. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a "CE sign-in report" entry to the registrants bulk-actions dropdown (CE events only), returning to the registrants page. On the report, each CE registrant gets an Edit link to their CE edit page. Report exposes ce_registration_for. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- CE edit opened from the sign-in report now returns there (eyebrow, Cancel, and after-save/destroy redirects all honor return_to=attendance, anchored to the totals section) - The report warns when an event outruns event_dates' 5-day cap, so missing day sections aren't mistaken for missing data - Drop the redundant single-column FK index (the composite covers it, declared in-table so MySQL doesn't auto-create one) and the unused EventAttendanceReport#any_entries? Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A stale remove (a second tab, browser-back on the turbo:false form, or a double-submit) sent a _destroy for an entry that was already gone, so nested attributes raised RecordNotFound and 500'd the save. Drop rows pointing at entries no longer on the registration so a stale remove is a quiet no-op. Also: one blank add-row instead of three (only saved entries get a Remove box, so three empties read as broken); the three form sections were touching, so wrap them in space-y-6; and when the sign-in window isn't open, show a standard gating notice naming the concrete opening (event zone, labeled) plus the rule, and hide the arrive/leave hint until sign-in is actually usable. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Move the all-days totals above the per-day sections and give them a teal treatment so the headline CE figures read first, with Day 1 / Day 2 below under a "By day" heading. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reject an entry (or nested batch) that pushes a day's logged time past 24 hours, or that overlaps another sign-in on the same day — you can't be signed in twice at once. Cross-entry checks read persisted rows plus the in-memory nested batch, so both self-service sign-in and the CE edit form are covered; self-service now redirects with the error instead of raising. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e links Add Day N logged columns (and per-day + hours-awarded totals in the All row) to the Totals table. Scope a registrant's Total logged to the event's days so it always equals the day columns — time logged on non-event dates no longer inflates it invisibly. Each row now links to the CE edit page and the name to that registrant's CE callout. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Put the "… logged" / "awarded" qualifier on a second line in smaller lowercase text, so the day columns stop crowding into one hard-to-read caps line. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Staff reading the sign-in sheet need the day's expected hours next to the
logged times to spot short days at a glance — reuse the event decorator's
times in the page header and each day header. Also tighten the CE callout's
sign-in-window note ("30 minutes before."), since the concrete opening
time already says when.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Callout page header: event dates move to their own line (the combined
title · date line wrapped awkwardly) and gain the daily hours
- The pre-window CE note names the opening time and, on its own line, the
event's actual start ("Event begins 30 min later, at 9:00 PDT") — a
countdown was tried and cut in favor of the concrete times
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
"Signed out" implied a session that never happened for first-time visitors — show only the Sign in button until an entry exists today. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- A sub-minute in/out pair counted as 0m; round up in the attendee's
favor so every completed session logs at least a minute
- Label the today's-entries columns with the display zone ("Time in
(PDT)") since bare clock times were ambiguous
- Sign out gets the primary CTA treatment while signed in (it's the only
action), and the button reads "Sign in again" once a session exists
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The stacked label/value rows made the card tall and left the status chip stranded mid-card — status now sits on the heading row, and the credit stats (hours at the same scale as cost) and license fields each share one wrapping row. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Staff viewing a registrant's CE page had no direct path to the event-wide report; reuse the admin_edit_link chip (gated on the attendance? policy) beside the Training sign-in heading. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Session detail now defaults to grouped-by-person (one card per registrant with a row per day), with a registrants-style pill toggle to the by-day tables — staff usually chase one person's hours, not a day - Non-CE events get the report too: participation rows and the registrants bulk-actions menu link a generic "Sign-in report" where the CE-scoped link doesn't apply Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Rows in both report groupings now open the CE edit page on the CE report and the registration edit page on the generic one — with return_to wiring (eyebrow + after-save redirect) added to the registration edit page so the round trip lands back on the report. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Forgetting to sign out is the failure mode this feature plans for, but an open entry had no date, so it carried into the next day of a multi-day training: sign-in was refused as "already signed in", and the only way out banked a ~24h session against the previous day (or tripped the daily limit and stranded the registrant entirely). Open entries are now scoped to the day being asked about, so each day starts fresh and the stale row stays flagged on the attendance report for staff to close. Two smaller fixes from the same review: - Drop the sign-in section once the last day's window has passed, instead of telling a registrant collecting their certificate that "Sign-in opens 30 minutes before" a training that's already over. - Render the staff time-entry rows from the in-memory association, so a save rejected for overlapping or backwards times re-renders with the admin's typed values instead of silently reverting them.
Review follow-ups on the attendance work:
A forgotten sign-out previously sat open until staff noticed it on the
report, which also under-counted the registrant's hours against the CE
certificate gate. It now gets its own catch-up button on the callout,
stamped with that day's scheduled end rather than "now" — the correction
staff made on the paper sheet — kept separate from today's sign-in/out so
the two days can't be confused.
The report's "signed in" chip was computed across every entry on the
registration but rendered inside a per-day row, so one forgotten sign-out
lit up every later day too — the opposite of what staff scan the report
for. Scope it to the day.
Attendance-entry validation messages reached the admin through the parent
registration's nested attributes, which pasted the humanized association
name in front of sentences written to display verbatim ("Event attendance
time entries This sign-in overlaps…").
The report's registrant-name links led to a callout whose eyebrow returned
to the CE edit page, leaving no way back to the report.
Co-Authored-By: Claude <noreply@anthropic.com>
Fixing one missed sign-in meant leaving the attendance report for the CE edit page, correcting a datetime, and navigating back — for a sheet whose whole point is scanning a training day at a glance. Each day's sessions cell now opens in place. Times are clock times, not datetimes: the day is the section the editor sits in, so a correction is two fields rather than two dates to retype and get wrong. A blank trailing row adds a session (including both halves of a day nobody signed in on), Remove drops one, and an empty sign-out leaves the session open. Registrants still stamp their own times from the CE callout — this is the correction surface, not a replacement for it. Server-rendered via an `edit` param rather than a Stimulus toggle, so a rejected save can hand the submitted times back through the flash and reopen the cell with them; the CE edit page keeps that property and the report shouldn't lose it. The whole-row link is suppressed while a cell is being edited so a stray click can't navigate away mid-correction. The nested-attributes write and audit stamping now live in EventAttendanceEntriesUpdate, shared with the CE edit form, and the verbatim-message error formatting moves to ApplicationController since two controllers now surface nested-association failures. Co-Authored-By: Claude <noreply@anthropic.com>
maebeale
force-pushed
the
maebeale/ce-attendance-report
branch
from
August 6, 2026 16:50
622ce96 to
b7dfb14
Compare
The CE-report name links once pointed the CE page eyebrow at return_to=ce_registration, stranding admins one hop from the report; assert the attendance value (and the old value's absence) so the eyebrow chain can't silently regress. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Both the sign-in-opening and event-begins times already show AM/PM; align the event-begins hour directive (%-l → %-I) so the adjacent times format identically. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🤖 suggested review level: 5 Inspect 🔬 new model + migration, public sign-in/out actions, an inline editor with its own write path, a certificate gate, and a per-event report across the CE callout + participation/registrants surfaces
Replaces AWBW's paper CE-hour sign-in sheet with in-portal sign-in/out, kept invisible to the general training group — surfaced only on each registrant's private CE callout, and only once CE is paid in full.
Why
What
EventAttendanceTimeEntry— generic per-day sign-in/out pairs (many/day for breaks), audited bycreated_by/updated_by.Event#attendance_sign_in_open?derives a per-day window from the event's single start/end time-of-day. Guards reject >24h logged on a day and any same-day overlap (checking persisted rows + the in-memory nested batch, so self-service and the edit forms are all covered).?entry_id) so closing one can't be mistaken for the other. Not offered when the sign-in lands after that day already ended — nothing sensible to stamp, so staff correct it on the report.EventAttendanceReport— per event, grouped by day then registrant. Leads with a teal Totals table (Day N logged columns, per-day + hours-awarded totals in the All row); a registrant's Total logged is scoped to the event's days so it always equals its day columns.?ce=truescopes to CE registrants and adds license # / awarded hours. Reached from CE-eligible events on the participation report and the registrants bulk-actions dropdown.?edit=<cell>→event_registrations#update_attendance). Clock times, not datetimes, since the day is the section it sits in; blank trailing row adds a session (including both halves of a day nobody signed in on), Remove drops one, blank sign-out leaves it open. A rejected save reopens the cell prefilled with what was typed.EventAttendanceEntriesUpdateholds the nested-attributes write + audit stamping, shared with the CE edit form.Notes
created_bynil); only staff edits are attributed.signed inon that day's report row.ApplicationController#error_sentencestops nested attributes pasting the humanized association name in front of them.day_countalready makes). Times use the request zone (Pacific), matching the paper sheet.Tests
Model/service/request/policy/view specs across every part, including the two entry guards, the day-boundary behaviour of an entry left open, the catch-up close (and when it's declined), the per-day scoping of the
signed inflag, every inline-editor path (add/correct/remove/leave-open/rejected-save/bad-date/authz), and total-vs-column consistency. Feature suite green.