Skip to content

Commit ae0c60b

Browse files
committed
add search to starred items
1 parent d5df36f commit ae0c60b

2 files changed

Lines changed: 126 additions & 27 deletions

File tree

app/controllers/stars_controller.rb

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,30 @@ class StarsController < ApplicationController
33

44
STARRABLE_TYPES = %w[Meeting AgendaItem CouncilMember Tag].freeze
55

6+
TYPE_LABELS = {
7+
"Meeting" => "Meetings",
8+
"AgendaItem" => "Agenda Items",
9+
"CouncilMember" => "Council Members",
10+
"Tag" => "Topics"
11+
}.freeze
12+
613
def index
7-
@stars_by_type = Current.user.stars.includes(:starrable).group_by(&:starrable_type)
14+
stars = Current.user.stars.includes(:starrable).order(created_at: :desc)
15+
16+
if params[:type].present? && STARRABLE_TYPES.include?(params[:type])
17+
@active_type = params[:type]
18+
stars = stars.where(starrable_type: @active_type)
19+
end
20+
21+
@stars = stars.select { |s| s.starrable.present? }
22+
23+
if params[:q].present?
24+
query = params[:q].downcase
25+
@stars = @stars.select { |s| star_matches_query?(s, query) }
26+
end
27+
28+
@type_counts = Current.user.stars.group(:starrable_type).count
29+
@total_count = @type_counts.values.sum
830
end
931

1032
def create
@@ -42,4 +64,21 @@ def find_starrable
4264
def dom_id_for_star_button(starrable)
4365
"star_button_#{starrable.class.name.underscore}_#{starrable.id}"
4466
end
67+
68+
def star_matches_query?(star, query)
69+
item = star.starrable
70+
case star.starrable_type
71+
when "Meeting"
72+
item.display_name.downcase.include?(query)
73+
when "AgendaItem"
74+
item.title.downcase.include?(query) || item.item_number.downcase.include?(query) ||
75+
item.file_number.to_s.downcase.include?(query)
76+
when "CouncilMember"
77+
item.display_name.downcase.include?(query)
78+
when "Tag"
79+
item.name.downcase.include?(query)
80+
else
81+
false
82+
end
83+
end
4584
end

app/views/stars/index.html.erb

Lines changed: 86 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,104 @@
11
<section class="p-6 sm:p-8">
2-
<h1 class="text-3xl font-bold text-gray-900 mb-8">Starred Items</h1>
2+
<h1 class="text-3xl font-bold text-gray-900 mb-6">Starred Items</h1>
33

4-
<% if @stars_by_type.empty? %>
4+
<% if @total_count > 0 %>
5+
<%# Filters %>
6+
<div class="flex flex-col sm:flex-row sm:items-center gap-3 mb-6">
7+
<%# Type pills %>
8+
<div class="flex flex-wrap gap-2">
9+
<%= link_to stars_path(q: params[:q]),
10+
class: "px-3 py-1.5 text-xs font-medium rounded-md transition-colors #{@active_type.blank? ? 'bg-gray-900 text-white' : 'bg-gray-100 text-gray-700 hover:bg-gray-200'}" do %>
11+
All <span class="ml-1 opacity-70"><%= @total_count %></span>
12+
<% end %>
13+
<% StarsController::TYPE_LABELS.each do |type, label| %>
14+
<% count = @type_counts[type] || 0 %>
15+
<% next if count == 0 %>
16+
<%= link_to stars_path(type: type, q: params[:q]),
17+
class: "px-3 py-1.5 text-xs font-medium rounded-md transition-colors #{@active_type == type ? 'bg-gray-900 text-white' : 'bg-gray-100 text-gray-700 hover:bg-gray-200'}" do %>
18+
<%= label %> <span class="ml-1 opacity-70"><%= count %></span>
19+
<% end %>
20+
<% end %>
21+
</div>
22+
23+
<%# Search %>
24+
<div class="sm:ml-auto">
25+
<%= form_with url: stars_path, method: :get, local: true, class: "flex gap-2" do %>
26+
<% if @active_type.present? %>
27+
<input type="hidden" name="type" value="<%= @active_type %>" />
28+
<% end %>
29+
<input type="text" name="q" value="<%= params[:q] %>" placeholder="Search starred items..."
30+
class="rounded-md border border-gray-300 px-3 py-1.5 text-sm shadow-sm focus:border-blue-500 focus:ring-blue-500 w-48 sm:w-56"
31+
autocomplete="off" />
32+
<button type="submit" class="px-3 py-1.5 bg-blue-600 text-white text-xs font-medium rounded-md hover:bg-blue-700 transition-colors cursor-pointer">Search</button>
33+
<% if params[:q].present? %>
34+
<%= link_to "Clear", stars_path(type: params[:type]), class: "px-3 py-1.5 text-xs font-medium text-gray-700 bg-gray-100 rounded-md hover:bg-gray-200 transition-colors flex items-center" %>
35+
<% end %>
36+
<% end %>
37+
</div>
38+
</div>
39+
<% end %>
40+
41+
<% if @total_count == 0 %>
542
<div class="bg-white rounded-lg border border-gray-200 p-12 text-center">
643
<p class="text-gray-500">You haven't starred any items yet. Star meetings, agenda items, council members, or topics for quick access.</p>
744
</div>
45+
<% elsif @stars.empty? %>
46+
<div class="bg-white rounded-lg border border-gray-200 p-12 text-center">
47+
<p class="text-gray-500">No starred items match your filters.</p>
48+
<%= link_to "Clear filters", stars_path, class: "mt-2 text-sm text-blue-600 hover:text-blue-800 inline-block" %>
49+
</div>
850
<% else %>
51+
<%# Group by type for display %>
52+
<% grouped = @stars.group_by(&:starrable_type) %>
953
<div class="space-y-8">
10-
<% { "Meeting" => "Meetings", "AgendaItem" => "Agenda Items", "CouncilMember" => "Council Members", "Tag" => "Topics" }.each do |type, label| %>
11-
<% if @stars_by_type[type]&.any? %>
12-
<div>
13-
<h2 class="text-xl font-semibold text-gray-900 mb-4"><%= label %></h2>
14-
<div class="bg-white rounded-lg border border-gray-200 divide-y divide-gray-100">
15-
<% @stars_by_type[type].each do |star| %>
16-
<% item = star.starrable %>
17-
<% next unless item %>
18-
<div class="px-6 py-4 flex items-center justify-between">
19-
<div>
20-
<% case type %>
21-
<% when "Meeting" %>
22-
<%= link_to item.display_name, meeting_path(item), class: "text-blue-600 hover:text-blue-800 font-medium" %>
23-
<% when "AgendaItem" %>
24-
<span class="text-sm text-gray-400"><%= item.item_number %></span>
25-
<span class="text-sm text-gray-900"><%= item.title.truncate(100) %></span>
26-
<% when "CouncilMember" %>
27-
<%= link_to item.display_name, council_member_path(item), class: "text-blue-600 hover:text-blue-800 font-medium" %>
28-
<% when "Tag" %>
29-
<%= link_to item.name, tag_path(item), class: "text-blue-600 hover:text-blue-800 font-medium" %>
54+
<% StarsController::TYPE_LABELS.each do |type, label| %>
55+
<% next unless grouped[type]&.any? %>
56+
<div>
57+
<h2 class="text-lg font-semibold text-gray-900 mb-3"><%= label %></h2>
58+
<div class="bg-white rounded-lg border border-gray-200 divide-y divide-gray-100">
59+
<% grouped[type].each do |star| %>
60+
<% item = star.starrable %>
61+
<div class="px-4 sm:px-6 py-3 flex items-center justify-between gap-3">
62+
<div class="min-w-0">
63+
<% case type %>
64+
<% when "Meeting" %>
65+
<%= link_to item.display_name, meeting_path(item), class: "text-sm text-blue-600 hover:text-blue-800 font-medium" %>
66+
<% when "AgendaItem" %>
67+
<div class="flex items-start gap-2">
68+
<span class="shrink-0 text-xs font-medium text-gray-500 bg-gray-100 rounded px-1.5 py-0.5"><%= item.item_number %></span>
69+
<div class="min-w-0">
70+
<p class="text-sm text-gray-900 truncate"><%= item.title.truncate(120) %></p>
71+
<div class="flex items-center gap-2 mt-0.5 text-xs text-gray-400">
72+
<% if item.file_number.present? %>
73+
<span><%= item.file_number %></span>
74+
<% end %>
75+
<span><%= item.item_type.titleize %></span>
76+
<% if item.meeting.present? %>
77+
<span><%= item.meeting.date.strftime("%b %-d, %Y") %></span>
78+
<% end %>
79+
</div>
80+
</div>
81+
</div>
82+
<% when "CouncilMember" %>
83+
<%= link_to item.display_name, council_member_path(item), class: "text-sm text-blue-600 hover:text-blue-800 font-medium" %>
84+
<% when "Tag" %>
85+
<%= link_to tag_path(item), class: "inline-flex items-center px-2.5 py-0.5 rounded text-xs font-medium bg-indigo-100 text-indigo-800 hover:bg-indigo-200 transition-colors" do %>
86+
<%= item.name %>
3087
<% end %>
31-
</div>
88+
<% end %>
89+
</div>
90+
<div class="shrink-0 flex items-center gap-2">
91+
<span class="text-[10px] text-gray-400 hidden sm:inline"><%= time_ago_in_words(star.created_at) %> ago</span>
3292
<%= button_to star_path(star), method: :delete, class: "text-yellow-500 hover:text-yellow-600 cursor-pointer", data: { turbo_stream: true } do %>
3393
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="currentColor">
3494
<path fill-rule="evenodd" d="M10.788 3.21c.448-1.077 1.976-1.077 2.424 0l2.082 5.006 5.404.434c1.164.093 1.636 1.545.749 2.305l-4.117 3.527 1.257 5.273c.271 1.136-.964 2.033-1.96 1.425L12 18.354 7.373 21.18c-.996.608-2.231-.29-1.96-1.425l1.257-5.273-4.117-3.527c-.887-.76-.415-2.212.749-2.305l5.404-.434 2.082-5.005Z" clip-rule="evenodd" />
3595
</svg>
3696
<% end %>
3797
</div>
38-
<% end %>
39-
</div>
98+
</div>
99+
<% end %>
40100
</div>
41-
<% end %>
101+
</div>
42102
<% end %>
43103
</div>
44104
<% end %>

0 commit comments

Comments
 (0)