diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index 17158ae..b1ba02f 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -22,6 +22,10 @@ html { -webkit-text-size-adjust: 100%; } +@media (prefers-reduced-motion: no-preference) { + html { scroll-behavior: smooth; } +} + body { min-height: 100vh; } img, svg { display: block; max-width: 100%; } @@ -360,71 +364,91 @@ hr { .day-nav { display: flex; justify-content: center; + column-gap: 1rem; + margin: 0 0 2.5rem; +} + +.day-nav a { + padding: 0.5rem; + color: #2672B5; + font-weight: 500; + text-decoration: underline; +} + + +/* ---------- Kind filter pills ----------------------------- */ + +.kind-filter { + display: flex; + justify-content: center; + align-items: center; flex-wrap: wrap; gap: 0.5rem; - margin: 1rem 0 3rem; + margin: 1rem 0 2rem; } -.day-nav a { +.kind-filter__label { + font-size: 0.95rem; + font-weight: 500; + color: #7a8189; + margin-right: 0.25rem; +} + +.kind-filter__btn { display: inline-block; padding: 0.375rem 1rem; border: 1px solid #d9dee1; border-radius: 999px; + background-color: transparent; color: #2672B5; font-weight: 500; font-size: 0.95rem; - transition: background-color 0.15s ease, border-color 0.15s ease; + transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease; } -.day-nav a:hover { +.kind-filter__btn:hover { background-color: #f4f7fa; border-color: #2672B5; opacity: 1; } - -/* ---------- Schedule day sections ------------------------- */ - -.schedule-day { - padding-top: 2rem; - border-top: 1px solid #d9dee1; +.kind-filter__btn--active { + background-color: #0C2866; + border-color: #0C2866; + color: #ffffff; } -.schedule-day + .schedule-day { - margin-top: 3rem; +.kind-filter__btn--active:hover { + background-color: #0C2866; + border-color: #0C2866; + color: #ffffff; } -.schedule-day__header { - display: flex; - flex-direction: column; - gap: 0.25rem; - margin-bottom: 1.5rem; -} -@media (min-width: 640px) { - .schedule-day__header { - flex-direction: row; - align-items: baseline; - gap: 1rem; - } +/* ---------- Schedule day sections (blueridgeruby.com style) -- */ + +.schedule-day { + margin-bottom: 5rem; } -.schedule-day__label { +.schedule-day__heading { font-size: 1.75rem; font-weight: 500; color: #0C2866; - line-height: 1.1; + text-align: center; + line-height: 1.2; + margin-bottom: 1rem; } -.schedule-day__date { - font-size: 1.125rem; - color: #404040; +.schedule-day__heading sup { + font-size: 0.6em; + vertical-align: super; + margin-left: 0.05em; } -.schedule-day__subtitle { - font-size: 0.95rem; - color: #7a8189; - font-style: italic; +.schedule-day__items { + border-top: 4px solid #0C2866; + padding-top: 0.25rem; } diff --git a/app/controllers/admin/schedule_items_controller.rb b/app/controllers/admin/schedule_items_controller.rb index c98e108..62ac073 100644 --- a/app/controllers/admin/schedule_items_controller.rb +++ b/app/controllers/admin/schedule_items_controller.rb @@ -3,7 +3,8 @@ class ScheduleItemsController < AdminController before_action :set_schedule_item, only: %i[edit update destroy] def index - @schedule_items = ScheduleItem.ordered + @selected_kind = ScheduleItem.kinds.key?(params[:kind].to_s) ? params[:kind] : nil + @schedule_items = ScheduleItem.by_kind(@selected_kind).ordered end def new diff --git a/app/controllers/schedule_controller.rb b/app/controllers/schedule_controller.rb index a4bddab..b7f6c48 100644 --- a/app/controllers/schedule_controller.rb +++ b/app/controllers/schedule_controller.rb @@ -1,6 +1,7 @@ class ScheduleController < ApplicationController def index - @items_by_day = ScheduleItem.public_items.ordered.group_by(&:day) - @planned_ids = current_user.plan_items.pluck(:schedule_item_id).to_set + @selected_kind = ScheduleItem.kinds.key?(params[:kind].to_s) ? params[:kind] : nil + @items_by_day = ScheduleItem.public_items.by_kind(@selected_kind).ordered.group_by(&:day) + @planned_ids = current_user.plan_items.pluck(:schedule_item_id).to_set end end diff --git a/app/models/schedule_item.rb b/app/models/schedule_item.rb index 698010c..9e30aa9 100644 --- a/app/models/schedule_item.rb +++ b/app/models/schedule_item.rb @@ -37,6 +37,10 @@ class ScheduleItem < ApplicationRecord :sort_time ) } + # Junk-safe: returns all rows when kind is blank or unknown. + scope :by_kind, ->(kind) { + kind.present? && kinds.key?(kind.to_s) ? where(kind: kind) : all + } # Creators always get auto-added to their own plan — whether the item is # private (only they see it) or public (others can RSVP). The rationale: diff --git a/app/views/admin/schedule_items/index.html.erb b/app/views/admin/schedule_items/index.html.erb index 0859123..bbb8368 100644 --- a/app/views/admin/schedule_items/index.html.erb +++ b/app/views/admin/schedule_items/index.html.erb @@ -5,51 +5,60 @@ <%= link_to "Add item", new_admin_schedule_item_path, class: "btn btn-red" %> - - - - - - - - - - - - - - <% @schedule_items.each do |item| %> +<%= render "shared/kind_filter", base_path: admin_schedule_items_path, selected_kind: @selected_kind %> + +<% if @schedule_items.empty? %> +

+ No items match this filter. + <%= link_to "Clear filter", admin_schedule_items_path, class: "text-blue underline" %> +

+<% else %> +
DayTimeInfoKindPublic?Host
+ - - - - - - - + + + + + + + - <% end %> - -
<%= ScheduleItem::DAY_META.dig(item.day, :label) || item.day %><%= item.time_label %> -
<%= item.title %>
- <% if item.location.present? %> -
<%= item.location %>
- <% end %> - <% if item.description.present? %> -
<%= item.description %>
- <% end %> -
- <% if item.kind.present? %> - <%= item.kind.humanize %> - <% else %> - unknown (<%= item.read_attribute_before_type_cast(:kind) %>) - <% end %> - <%= item.is_public? ? "Public" : "Private" %><%= item.host.presence || "—" %> -
- <%= link_to "Edit", edit_admin_schedule_item_path(item), class: "btn btn-muted" %> - <%= button_to "Delete", admin_schedule_item_path(item), - method: :delete, - data: { turbo_confirm: "Delete '#{item.title}'? RSVPs will also be removed." }, - class: "btn btn-red" %> -
-
DayTimeInfoKindPublic?Host
+ + + <% @schedule_items.each do |item| %> + + <%= ScheduleItem::DAY_META.dig(item.day, :label) || item.day %> + <%= item.time_label %> + +
<%= item.title %>
+ <% if item.location.present? %> +
<%= item.location %>
+ <% end %> + <% if item.description.present? %> +
<%= item.description %>
+ <% end %> + + + <% if item.kind.present? %> + <%= item.kind.humanize %> + <% else %> + unknown (<%= item.read_attribute_before_type_cast(:kind) %>) + <% end %> + + <%= item.is_public? ? "Public" : "Private" %> + <%= item.host.presence || "—" %> + +
+ <%= link_to "Edit", edit_admin_schedule_item_path(item), class: "btn btn-muted" %> + <%= button_to "Delete", admin_schedule_item_path(item), + method: :delete, + data: { turbo_confirm: "Delete '#{item.title}'? RSVPs will also be removed." }, + class: "btn btn-red" %> +
+ + + <% end %> + + +<% end %> diff --git a/app/views/schedule/_day.html.erb b/app/views/schedule/_day.html.erb index 79765e8..33413b3 100644 --- a/app/views/schedule/_day.html.erb +++ b/app/views/schedule/_day.html.erb @@ -1,11 +1,12 @@ +<% + month, day_num_str = meta[:date].to_s.split(" ", 2) + day_num = day_num_str.to_i + ordinal_suffix = day_num.ordinalize.delete("0-9") +%>
-
-

<%= meta[:label] %>

- <%= meta[:date] %> - <% if meta[:subtitle].present? %> - <%= meta[:subtitle] %> - <% end %> -
+

+ <%= meta[:label] %>, <%= month %> <%= day_num %><%= ordinal_suffix %> +

<% items.each do |item| %> diff --git a/app/views/schedule/index.html.erb b/app/views/schedule/index.html.erb index b7faac1..17333d6 100644 --- a/app/views/schedule/index.html.erb +++ b/app/views/schedule/index.html.erb @@ -10,17 +10,26 @@ Browse the full conference agenda. Tap Add to plan on any talk or RSVP on any activity to save it to your Plan.

- - - <% ScheduleItem::DAY_META.each do |day_key, meta| %> - <% items = @items_by_day[day_key] %> - <% next if items.blank? %> - <%= render "day", day_key: day_key, meta: meta, items: items, planned_ids: @planned_ids %> <% end %>
diff --git a/app/views/shared/_kind_filter.html.erb b/app/views/shared/_kind_filter.html.erb new file mode 100644 index 0000000..2118eb2 --- /dev/null +++ b/app/views/shared/_kind_filter.html.erb @@ -0,0 +1,19 @@ +<%# + locals: + base_path - String, e.g. schedule_path or admin_schedule_items_path + selected_kind - String or nil, the currently active kind +%> +<% qp_clear = request.query_parameters.except(:kind) %> +