Skip to content

Admin Dashboard redesign + Embassy backend + sortable Applications queue#15

Merged
Kitkatnik merged 18 commits intomainfrom
Kitkatnik/admin-dashboard-ui
Apr 26, 2026
Merged

Admin Dashboard redesign + Embassy backend + sortable Applications queue#15
Kitkatnik merged 18 commits intomainfrom
Kitkatnik/admin-dashboard-ui

Conversation

@Kitkatnik
Copy link
Copy Markdown
Collaborator

Summary

  • Merges the real Embassy backend (EmbassyApplication/EmbassyBooking/Question/NotaryProfile models, adjudication services, and PDF generation), replacing the FakeEmbassy mock service.
  • Redesigns the admin dashboard with at-a-glance stats + a Manage tile grid, and reworks the admin navbar (Admin Dashboard · Attendees · Volunteers (soon) · Embassy Applications · User Dashboard).
  • Adds a sortable + searchable Embassy Applications admin queue with a "Mark Received" passport-delivery flow, plus a separate Delivered Passports page with Undo.
  • New migration adds passport_received_at to embassy_applications; a small ApplicationHelper#sort_link helper preserves search/sort state across column-header clicks.

Test plan

  • Visit /admin — stats row + 7 Manage tiles (Attendees, Volunteers (soon), Embassy Applications, Embassy Question Bank, Generate Blank Forms, Schedule Items, Background Jobs)
  • /admin/embassy_applications — sort by Serial / Attendee / Appointment, search by name/email/serial, "Mark Received" moves a row to the Delivered queue
  • /admin/embassy_applications/delivered — "Undo" returns a row to the active queue
  • bin/rails db:migrate runs cleanly on a fresh database
  • Public navbar "Admin" link lands on /admin (not /admin/users)

- Add Question, NotaryProfile, EmbassyBooking, EmbassyApplication, and
  EmbassyApplicationAnswer models, with embassy_mode/embassy_capacity
  columns on ScheduleItem.
- Seed 80 questions and 18 notaries from db/seeds/embassy_questions.rb
  via idempotent EmbassyQuestionsSeed.import! (keyed by external_id).
- Wire EmbassyBookings, EmbassyApplications, and the Admin::* controllers
  to real ActiveRecord queries in place of FakeEmbassy.
- Generate real PDF downloads with Prawn (PassportApplicationPdf, formal
  tax-form aesthetic), replacing the browser-print HTML partial.
- Draw Section 3 questions and notary by least-used-first
  (EmbassyApplicationDraw) for fair distribution across applicants.
- Enforce booking capacity inside a row-locked transaction; support
  edit-while-draft on EmbassyApplication.
- Add Stimulus controller to toggle the embassy capacity/mode fields on
  the admin schedule-item form when kind = embassy.
- Delete fake_embassy.rb, _pdf.html.erb, _expired.html.erb.
Switch from f.submit (which renders <input type="submit"> where the value
attribute IS the displayed label) to <button type="submit"> elements with
separate inner text and submitted value. The button now shows "Submit
Application" instead of the literal "1" that was being submitted as the
form value. Renames the discriminator parameter from `submit` to `intent`
for clarity.
Previously "Save & Return Later" was a link that navigated away without
saving any answers — confusing alongside a separate "Save draft" button
that did save. Removes the redundant Save draft button and converts
"Save & Return Later" into a submit button (intent=draft) that saves the
in-progress answers and redirects to /plan.

Extracts the shared "submit vs. draft" branch into finalize_or_redirect
so both create and update go through one path.
PDF layout overhaul addressing five visual issues:

- Application now spans 2 pages instead of 8+. Short fields (Given Name +
  GitHub handle, Pronouns + Age in Coding Years, etc.) render side-by-side
  in two columns. Long answers get more vertical room sized from
  question.max_length.
- Add max_length column on Question, surfaced on the admin form and as
  HTML maxlength on the user-facing form. Defaults: 60 chars (short),
  240 chars (long). Caps text so it always fits the printed PDF box.
- Page footer now uses pdf.repeat(:all, dynamic: true) inside pdf.canvas
  so it renders in the bottom margin (not the content area) and shows
  the actual page number on every page (was always "Page 2 of 2").
- Applicant signature + date side-by-side in fixed-position bounding
  boxes — they now align horizontally (was vertically stacked due to
  fragile move_up arithmetic).
- Notary printed name + date aligned the same way; signature + notary
  ID below them no longer overlap.

Root-cause fix for the runaway pagination: boxed_text's inner
bounding_box had height < line height for short fields, which silently
triggered Prawn auto-pagination. Switched to draw_text/text_box for
single-line fields and made the inner-box height calculations safe.
PDF refinements:
- Render checkbox_group options inline with formatted_text fragments so
  short option lists ("Learning / Networking / Vibes / Free coffee") fit
  on one line; longer lists wrap naturally to 2 lines.
- Add INSTRUCTIONS TO THE APPLICANT (4 paragraphs) and EMBASSY
  ORDINANCES (7 §-numbered clauses) after the signature block to fill
  the otherwise mostly-blank page 2 with parodic legalese.
- Rename header subtitle from "United Embassy of Ruby" to
  "Blue Ridge Ruby Embassy" on both application + notary pages.

Confirmation page:
- Reword photography etiquette to ask about consent for photographing
  other attendees instead of the (fictional) Stamping Apparatus.
The inline checkbox rendering works well for short option lists like
Section 1.7 (Learning / Networking / Vibes / Free coffee) but turns
Section 4's longer affirmations into a hard-to-scan run-on. Branch
on a heuristic: 6+ options OR any option > 30 characters falls back
to the 2-column grid. Section 4 (7 options, ~50 chars each) now uses
the grid; Sections 1.7-1.9 keep the inline layout.
- APPLICANT SIGNATURE box left blank for physical ink signing; caption
  reads "Signature of Applicant" (drops "(typed)" — the typed name is
  already captured above as 5.4).
- §2 Discretion: replaces "tabs over spaces" jab with "documented hatred
  of the Ruby programming language" — fits the Embassy theme.
- §5 Right of Appeal: replaces /dev/null gag with noreply@blueridgeruby.com
  so the appeal channel is at least nominally plausible.
- Instruction #2: rewritten generically ("Answer all questions as
  printed...") to keep Section 3's randomization a surprise — attendees
  comparing applications will see different questions and not realize
  why.

Removes the now-unused PassportApplicationPdf#answer_text helper.
The inline formatted_text path packed options as natural-width
fragments, so spacing varied between every option. Replaces it with a
universal grid layout that adapts column count from option width:

  - Short options (max ~12 chars): 4 columns
  - Medium phrases (max ~25 chars): 3 columns
  - Long affirmations (Section 4): 2 columns

Each option gets a fixed-width slot, so they line up vertically within
a question — even across multiple rows. Drops the two_column_options?
branch since one grid path now handles all sizes.
- Quick-link action cards on top, 4 stat cards on the bottom
- Navbar: Admin Dashboard, Attendees, Volunteers (Soon), Embassy Applications
- "User Dashboard" replaces "My dashboard" on the right side
- Public-nav "Admin" link now goes to admin_root_path (was admin_users_path)
- Add missing utility classes (grid-cols-N, md:grid-cols-N, mb-10) so the
  dashboard grid actually renders multi-column instead of stacking
- Dashboard "Embassy Applications" tile now reflects real DB count
  (submitted apps awaiting passport delivery).
- Admin embassy applications page splits into two queues:
  * Active queue (default): submitted, passport not yet received.
  * Delivered Passports page: linked from active queue, undo-able.
- Sort by Serial / Attendee name / Appointment via sortable column
  headers (Sort whitelisted with Arel.sql wrap to satisfy ActiveRecord).
- Search box filters by attendee name, email, or serial (ILIKE).
- "Mark Received" stamps passport_received_at; "Undo" clears it.
- New migration: add nullable passport_received_at:datetime.
- New ApplicationHelper#sort_link preserves current path + ?q= search
  while toggling the active sort; gets a small ▾ marker when active.
- Reorder responsive grid utilities so md:grid-cols-N actually wins over
  base grid-cols-1 in the cascade (they were declared earlier in the
  source so the base was overriding them at all viewports — the grid
  silently collapsed to a single column).
- Widen dashboard container from max-w-4xl to max-w-6xl to use the
  available width.
- Put stats above quick links so the most informative content is at the
  top of the page.
- Add .action-card--compact modifier (smaller padding, title-only) and
  drop per-card descriptions from the dashboard. Six tiles fit in a
  2x3 grid (3-up on md+), keeping the dashboard above the fold.
… stat

- Tile order: Attendees, Volunteers (soon), Embassy Applications,
  Embassy Question Bank, Generate Blank Forms, Schedule Items,
  Background Jobs.
- Replace the confusing "Hosted Activities / X public schedule items"
  tile (two unrelated counts) with a single "Schedule Items" tile that
  shows the total + a kind breakdown (N talks · N activities · N embassy)
  in the sub-label.
Drop the action-card--compact modifier and put back the per-tile
descriptions. Volunteers tile keeps its 'Soon' badge inline with
its own description copy.
The headline number speaks for itself; remove the breakdown sub-text
and the now-unused @schedule_items_breakdown query.
@railway-app
Copy link
Copy Markdown

railway-app Bot commented Apr 26, 2026

🚅 Deployed to the ruby-embassy-pr-15 environment in ruby-embassy

Service Status Web Updated (UTC)
ruby-embassy ✅ Success (View Logs) Web Apr 26, 2026 at 5:16 am

@railway-app railway-app Bot temporarily deployed to ruby-embassy / ruby-embassy-pr-15 April 26, 2026 05:12 Destroyed
- Apply rubocop -a layout fixes (SpaceInsideArrayLiteralBrackets,
  SpaceBeforeFirstArg) across the embassy backend code that came in
  via the merge.
- Empty the auto-generated embassy fixtures (Application, Booking,
  Answer, Question, NotaryProfile). They referenced non-existent
  user/schedule_item/plan_item rows and had MyString/nil values that
  violated NOT NULL constraints, breaking fixture loading for every
  test in the suite. The matching test files are empty placeholders
  anyway, matching the existing pattern in plan_items.yml /
  schedule_items.yml.
@railway-app railway-app Bot temporarily deployed to ruby-embassy / ruby-embassy-pr-15 April 26, 2026 05:16 Destroyed
@Kitkatnik Kitkatnik merged commit 0e379f5 into main Apr 26, 2026
6 checks passed
@Kitkatnik Kitkatnik deleted the Kitkatnik/admin-dashboard-ui branch April 26, 2026 07:02
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