feat(reports): counter for meeting attendance records#5291
Conversation
Adds a touch-friendly counter for tallying meeting attendance on phones, tablets, and narrow desktop windows. Focusing a count field reveals a "Counter" suggestion button that opens a full-screen overlay with large +/- controls, so attendance can be tapped in without the keyboard. - Reusable components: TabSwitcher (segmented control with icon/label and ellipsis handling) and SubpageHeader (overlay top bar that inherits the parent page title so it can't drift) - Odometer display with per-digit ribbon roll, leading-zero collapse, and a count-down on reset - Present/Online counts crossfade so they read as separate counters, with swipe-to-switch (touch and mouse) - Atomic save of both counts in one write (meetingAttendanceCountsSave) - Shown only on compact viewports or touch devices (useIsTouchDevice), including large iPads
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds a clicker-mode attendance editor with animated counters, touch and swipe interactions, reusable navigation controls, field suggestions, haptic feedback settings, and atomic persistence for present and online attendance counts. ChangesClicker attendance flow
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant WeekBox
participant ClickerSuggestion
participant ClickerMode
participant useClickerMode
participant AttendanceService
participant Haptics
WeekBox->>ClickerSuggestion: show focused-field suggestion
ClickerSuggestion->>ClickerMode: open clicker dialog
ClickerMode->>useClickerMode: edit active attendance count
useClickerMode->>Haptics: trigger tap, limit, or reset intent
useClickerMode->>ClickerMode: return count and interaction signals
ClickerMode->>WeekBox: submit touched present/online values
WeekBox->>AttendanceService: save counts for the attendance row
Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
- Dialog: add aria-label so the full-screen overlay has an accessible name - AnimatedCount: hide the odometer ribbon from the a11y tree and expose the real numeric value via a visually-hidden element - CounterPad: localize the +/- aria-labels (tr_increase / tr_decrease) - SubpageHeader: make backLabel required so the back button always has a name - AnimatedCount: use nullish assignment for the tween start timestamp
Keep only short notes in the complex spots (odometer ribbon, crossfade, swipe); remove the obvious ones. No behavior change.
Odometer ribbon cells keyed by a stable id; confetti pieces by their unique start column. Addresses Sonar 'Do not use Array index in keys'.
The icon already existed on main as plain paths (one of ~29 mask-less icons). The added <mask>/<g> was a full-coverage no-op — reverting removes the diff noise and the duplicate-id concern, leaving a pre-existing file untouched.
Remove prop-by-prop JSDoc from the type files and other self-evident comments; keep only the non-obvious 'why' notes. No behavior change.
|
Tick the box to add this pull request to the merge queue (same as
|
Condense the kept 'why' notes to single tight lines; no behavior change.
5af9bb2 to
c6b73f6
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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/features/reports/meeting_attendance/monthly_record/clicker_mode/animated_count/index.tsx`:
- Around line 211-218: Update the visually hidden count element in the animated
count component to include an appropriate aria-live attribute so screen readers
announce changes to display as it updates. Keep the existing visuallyHidden
styling and rendered value unchanged.
In `@src/features/reports/meeting_attendance/monthly_record/week_box/index.tsx`:
- Around line 65-86: Move the blur handling from each TextField to its wrapping
Box in both the present-field block
(src/features/reports/meeting_attendance/monthly_record/week_box/index.tsx#L65-L86)
and online-field block
(src/features/reports/meeting_attendance/monthly_record/week_box/index.tsx#L98-L119).
Update the container-level handlers to inspect event.relatedTarget and avoid
calling handleFieldBlur when focus moves to the ClickerSuggestion button within
that same container.
🪄 Autofix (Beta)
✅ Autofix completed
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: d11fe56b-daa6-4e69-ba29-06a169fe970d
⛔ Files ignored due to path filters (1)
src/locales/en/meetings.jsonis excluded by!**/*.json
📒 Files selected for processing (23)
src/components/subpage_navbar/index.tsxsrc/components/subpage_navbar/index.types.tssrc/components/tab_switcher/index.tsxsrc/components/tab_switcher/index.types.tssrc/features/reports/meeting_attendance/monthly_record/clicker_mode/animated_count/index.tsxsrc/features/reports/meeting_attendance/monthly_record/clicker_mode/animated_count/index.types.tssrc/features/reports/meeting_attendance/monthly_record/clicker_mode/confetti/index.tsxsrc/features/reports/meeting_attendance/monthly_record/clicker_mode/count_swap/index.tsxsrc/features/reports/meeting_attendance/monthly_record/clicker_mode/count_swap/index.types.tssrc/features/reports/meeting_attendance/monthly_record/clicker_mode/counter_pad/index.tsxsrc/features/reports/meeting_attendance/monthly_record/clicker_mode/counter_pad/index.types.tssrc/features/reports/meeting_attendance/monthly_record/clicker_mode/index.styles.tssrc/features/reports/meeting_attendance/monthly_record/clicker_mode/index.tsxsrc/features/reports/meeting_attendance/monthly_record/clicker_mode/index.types.tssrc/features/reports/meeting_attendance/monthly_record/clicker_mode/suggestion_button/index.tsxsrc/features/reports/meeting_attendance/monthly_record/clicker_mode/suggestion_button/index.types.tssrc/features/reports/meeting_attendance/monthly_record/clicker_mode/useClickerMode.tsxsrc/features/reports/meeting_attendance/monthly_record/week_box/index.styles.tssrc/features/reports/meeting_attendance/monthly_record/week_box/index.tsxsrc/features/reports/meeting_attendance/monthly_record/week_box/useWeekBox.tsxsrc/hooks/index.tssrc/hooks/useIsTouchDevice.tsxsrc/services/app/meeting_attendance.ts
🚧 Files skipped from review as they are similar to previous changes (19)
- src/hooks/index.ts
- src/features/reports/meeting_attendance/monthly_record/clicker_mode/animated_count/index.types.ts
- src/features/reports/meeting_attendance/monthly_record/clicker_mode/count_swap/index.types.ts
- src/features/reports/meeting_attendance/monthly_record/clicker_mode/counter_pad/index.types.ts
- src/features/reports/meeting_attendance/monthly_record/clicker_mode/suggestion_button/index.types.ts
- src/features/reports/meeting_attendance/monthly_record/clicker_mode/confetti/index.tsx
- src/components/tab_switcher/index.types.ts
- src/features/reports/meeting_attendance/monthly_record/week_box/index.styles.ts
- src/features/reports/meeting_attendance/monthly_record/clicker_mode/suggestion_button/index.tsx
- src/features/reports/meeting_attendance/monthly_record/clicker_mode/useClickerMode.tsx
- src/features/reports/meeting_attendance/monthly_record/clicker_mode/index.styles.ts
- src/hooks/useIsTouchDevice.tsx
- src/features/reports/meeting_attendance/monthly_record/clicker_mode/index.tsx
- src/features/reports/meeting_attendance/monthly_record/week_box/useWeekBox.tsx
- src/features/reports/meeting_attendance/monthly_record/clicker_mode/index.types.ts
- src/features/reports/meeting_attendance/monthly_record/clicker_mode/count_swap/index.tsx
- src/features/reports/meeting_attendance/monthly_record/clicker_mode/counter_pad/index.tsx
- src/components/tab_switcher/index.tsx
- src/services/app/meeting_attendance.ts
|
Note Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it. Fixes Applied SuccessfullyFixed 2 file(s) based on 2 unresolved review comments. Files modified:
Commit: The changes have been pushed to the Time taken: |
Fixed 2 file(s) based on 2 unresolved review comments. Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
f0d4725 to
93c78e7
Compare
Counting now registers on pointer-down rather than click, so the first tap after switching tabs is no longer swallowed. This matters because the whole point of clicker mode is tallying without looking at the screen. Keyboard activation still works through the click path (detail === 0), and the tiles opt out of tap delay and double-tap zoom. The reset arrow is mirrored to point counter-clockwise, spins in the matching direction, and now stays on screen for the full 500ms spin before fading, so the animation can actually be seen. Decrementing at 0 is a no-op instead of marking the tab touched. Adds a shared haptics service (@services/haptics) wrapping web-haptics, with semantic intents rather than raw millisecond arrays: a tap per count, a denial pattern mirroring the 400ms shake when the 1914 cap is refused, and a decaying buzz plus final pulse for reset. Gated centrally by a new haptics_enabled user setting, which is only shown on touch devices.
93c78e7 to
8e6523c
Compare
The reset's closing pulse landed at 550ms, which is exactly when the odometer finishes tweening to 0, so it read as arriving after the fact. The decay is now compressed and the pulse fires at ~420ms, while the digits are still settling onto 0. Reaching 1914 also plays a light six-impulse "prrr" alongside the confetti burst, fired from the same place the burst is triggered so the two can't drift apart.
The 22px spacer that balances the back button held its width even while the title was truncating, which long translations noticed. It now shrinks away before the title has to truncate: the title takes flex-basis auto so an oversized title actually overflows the row and triggers a shrink pass, and the spacer carries a high shrink factor so it yields first. Nothing moves while the title fits, so short titles stay centred exactly as before; a long title gains the full 22px.
The counter tiles and reset button took their pressed look from CSS :active, which the browser skips for the first press right after focus moves (switching tabs). Drive the pressed state from pointer events via an is-pressed class so that press is styled as clicked too.
|



Description
Adds Counter — a touch-friendly way to tally meeting attendance on phones and tablets, so attendance can be counted without watching the device screen.
What was added
meetingAttendanceCountsSave) — avoids dropping a value when present + online are saved together.useIsTouchDevice), including large iPads whose width reads like a laptop's.Counting reliability & polish
detail === 0), and the tiles opt out of tap delay and double-tap zoom.New reusable global components
Two general-purpose components were added under
src/components/, intended to be reused across other pages, not just here:TabSwitcherA generic segmented control (iOS-style toggle):
value/onChangetyping.SubpageNavbarThe app's subpage navigation bar (back button, title, subtitle, optional trailing action), extracted into a reusable component so overlays/dialogs that live outside the router can use it too:
tablet688up.secondaryTitleis passed, it inherits the parent page's title fromnavBarOptionsState(single source of truth), so it can't drift when the page is renamed.New reusable global service
@services/hapticsHaptics is introduced here as a shared, app-wide service, not as clicker-only code — the counter is simply its first consumer:
haptic('tap'),haptic('limit'),haptic('reset')) rather than raw millisecond arrays. Patterns live in one table (patterns.ts), so the feel of every surface is tuned in a single place and new surfaces reuse existing intents.New user setting — Haptic feedback under Organized settings (directly below automatic theme change), shown only on touch devices since vibration cannot exist elsewhere. Defaults on. Registered through the usual path:
user_settingstype + schema default, jotai atom, encryption map, and the pocket backup payload.Why a dependency (
web-haptics, MIT): the Vibration API has no amplitude control, and iOS Safari does not implement it at all. The library simulates intensity via PWM on Android and drives the iOS 17.4+ switch-toggle fallback — both of which we would otherwise have to hand-roll. It is wrapped behind our own service so the engine stays swappable.Type of change
Checklist:
Notes for reviewers
tsc --noEmitintroduces 0 new errors (repo baseline unchanged); production build passes.tr_clickerMode="Counter"(plustr_increase/tr_decrease,tr_hapticFeedback) added to the English source; other locales flow through Crowdin.aria-hiddenwith the real value exposed to screen readers, and the +/− controls have localized labels.web-haptics(MIT). Rationale above.