Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 57 additions & 33 deletions app/assets/stylesheets/application.css
Original file line number Diff line number Diff line change
Expand Up @@ -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%; }
Expand Down Expand Up @@ -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;
}


Expand Down
3 changes: 2 additions & 1 deletion app/controllers/admin/schedule_items_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions app/controllers/schedule_controller.rb
Original file line number Diff line number Diff line change
@@ -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
4 changes: 4 additions & 0 deletions app/models/schedule_item.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
101 changes: 55 additions & 46 deletions app/views/admin/schedule_items/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -5,51 +5,60 @@
<%= link_to "Add item", new_admin_schedule_item_path, class: "btn btn-red" %>
</div>

<table class="table">
<thead>
<tr>
<th>Day</th>
<th>Time</th>
<th>Info</th>
<th>Kind</th>
<th>Public?</th>
<th>Host</th>
<th></th>
</tr>
</thead>
<tbody>
<% @schedule_items.each do |item| %>
<%= render "shared/kind_filter", base_path: admin_schedule_items_path, selected_kind: @selected_kind %>

<% if @schedule_items.empty? %>
<p class="text-gray py-8">
No items match this filter.
<%= link_to "Clear filter", admin_schedule_items_path, class: "text-blue underline" %>
</p>
<% else %>
<table class="table">
<thead>
<tr>
<td><%= ScheduleItem::DAY_META.dig(item.day, :label) || item.day %></td>
<td><%= item.time_label %></td>
<td>
<div class="font-medium text-navy"><%= item.title %></div>
<% if item.location.present? %>
<div class="text-gray text-sm"><%= item.location %></div>
<% end %>
<% if item.description.present? %>
<div class="text-gray text-sm"><%= item.description %></div>
<% end %>
</td>
<td>
<% if item.kind.present? %>
<span class="item-badge item-badge--<%= item.kind %>"><%= item.kind.humanize %></span>
<% else %>
<span class="item-badge text-muted">unknown (<%= item.read_attribute_before_type_cast(:kind) %>)</span>
<% end %>
</td>
<td><%= item.is_public? ? "Public" : "Private" %></td>
<td><%= item.host.presence || "—" %></td>
<td class="text-right">
<div class="flex gap-2 justify-end">
<%= 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" %>
</div>
</td>
<th>Day</th>
<th>Time</th>
<th>Info</th>
<th>Kind</th>
<th>Public?</th>
<th>Host</th>
<th></th>
</tr>
<% end %>
</tbody>
</table>
</thead>
<tbody>
<% @schedule_items.each do |item| %>
<tr>
<td><%= ScheduleItem::DAY_META.dig(item.day, :label) || item.day %></td>
<td><%= item.time_label %></td>
<td>
<div class="font-medium text-navy"><%= item.title %></div>
<% if item.location.present? %>
<div class="text-gray text-sm"><%= item.location %></div>
<% end %>
<% if item.description.present? %>
<div class="text-gray text-sm"><%= item.description %></div>
<% end %>
</td>
<td>
<% if item.kind.present? %>
<span class="item-badge item-badge--<%= item.kind %>"><%= item.kind.humanize %></span>
<% else %>
<span class="item-badge text-muted">unknown (<%= item.read_attribute_before_type_cast(:kind) %>)</span>
<% end %>
</td>
<td><%= item.is_public? ? "Public" : "Private" %></td>
<td><%= item.host.presence || "—" %></td>
<td class="text-right">
<div class="flex gap-2 justify-end">
<%= 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" %>
</div>
</td>
</tr>
<% end %>
</tbody>
</table>
<% end %>
15 changes: 8 additions & 7 deletions app/views/schedule/_day.html.erb
Original file line number Diff line number Diff line change
@@ -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")
%>
<section class="schedule-day" id="<%= day_key %>">
<header class="schedule-day__header">
<h2 class="schedule-day__label"><%= meta[:label] %></h2>
<span class="schedule-day__date"><%= meta[:date] %></span>
<% if meta[:subtitle].present? %>
<span class="schedule-day__subtitle"><%= meta[:subtitle] %></span>
<% end %>
</header>
<h3 class="schedule-day__heading">
<%= meta[:label] %>, <%= month %> <%= day_num %><sup><%= ordinal_suffix %></sup>
</h3>

<div class="schedule-day__items">
<% items.each do |item| %>
Expand Down
27 changes: 18 additions & 9 deletions app/views/schedule/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,26 @@
Browse the full conference agenda. Tap <span class="font-medium">Add to plan</span> on any talk or <span class="font-medium">RSVP</span> on any activity to save it to your Plan.
</p>

<nav class="day-nav" aria-label="Jump to day">
<%= render "shared/kind_filter", base_path: schedule_path, selected_kind: @selected_kind %>

<% if @items_by_day.empty? %>
<p class="text-center text-gray py-12">
No items match this filter.
<%= link_to "Clear filter", schedule_path, class: "text-blue underline" %>
</p>
<% else %>
<nav class="day-nav" aria-label="Jump to day">
<% ScheduleItem::DAY_META.each do |day_key, meta| %>
<% next unless @items_by_day.key?(day_key) %>
<a href="#<%= day_key %>"><%= meta[:label] %></a>
<% end %>
</nav>

<% ScheduleItem::DAY_META.each do |day_key, meta| %>
<% next unless @items_by_day.key?(day_key) %>
<a href="#<%= day_key %>"><%= meta[:label] %></a>
<% 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 %>
</nav>

<% 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 %>
</div>
</section>
19 changes: 19 additions & 0 deletions app/views/shared/_kind_filter.html.erb
Original file line number Diff line number Diff line change
@@ -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) %>
<nav class="kind-filter" aria-label="Filter by kind">
<span class="kind-filter__label">Filter:</span>

<%= link_to "All",
base_path + (qp_clear.any? ? "?#{qp_clear.to_query}" : ""),
class: "kind-filter__btn #{'kind-filter__btn--active' if selected_kind.blank?}" %>

<% ScheduleItem.kinds.each_key do |kind| %>
<%= link_to kind.humanize,
base_path + "?#{request.query_parameters.merge(kind: kind).to_query}",
class: "kind-filter__btn #{'kind-filter__btn--active' if selected_kind == kind}" %>
<% end %>
</nav>
Loading