feat: add city search functionality
- Implement search_by_name scope in City model - Add SearchController for handling search input - Include _search_city partial in cities index view - Update cities_controller to filter cities based on search query This commit introduces a new feature that allows users to search for cities by name using an input field. The search is implemented as a scope in the City model, and it is integrated into the existing CitiesController. A dedicated SearchController manages the input submission with a debouncing mechanism for better performance. The search field is rendered in the cities index view, enhancing user interactivity and experience.
This commit is contained in:
parent
51d626a67f
commit
799f3222a9
@ -6,6 +6,10 @@ class CitiesController < ApplicationController
|
|||||||
@regions = Region.includes(:countries).order(:name)
|
@regions = Region.includes(:countries).order(:name)
|
||||||
@cities = City.includes(:country, country: :region).order(:name)
|
@cities = City.includes(:country, country: :region).order(:name)
|
||||||
|
|
||||||
|
if params[:query].present?
|
||||||
|
@cities = @cities.search_by_name(params[:query])
|
||||||
|
end
|
||||||
|
|
||||||
if params[:region]
|
if params[:region]
|
||||||
@current_region = Region.friendly.find(params[:region])
|
@current_region = Region.friendly.find(params[:region])
|
||||||
@cities = @cities.by_region(@current_region.id) if @current_region
|
@cities = @cities.by_region(@current_region.id) if @current_region
|
||||||
|
@ -6,7 +6,9 @@ import { application } from "./application"
|
|||||||
import HelloController from "./hello_controller"
|
import HelloController from "./hello_controller"
|
||||||
import PhotoSwipeLightBoxController from "./photo_swipe_lightbox_controller"
|
import PhotoSwipeLightBoxController from "./photo_swipe_lightbox_controller"
|
||||||
import FlashMessageController from "./flash_controller"
|
import FlashMessageController from "./flash_controller"
|
||||||
|
import SearchController from "./search_controller"
|
||||||
|
|
||||||
application.register("hello", HelloController)
|
application.register("hello", HelloController)
|
||||||
application.register("photo-swipe-lightbox", PhotoSwipeLightBoxController)
|
application.register("photo-swipe-lightbox", PhotoSwipeLightBoxController)
|
||||||
application.register("flash", FlashMessageController)
|
application.register("flash", FlashMessageController)
|
||||||
|
application.register("search", SearchController)
|
||||||
|
13
app/javascript/controllers/search_controller.js
Normal file
13
app/javascript/controllers/search_controller.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// app/javascript/controllers/search_controller.js
|
||||||
|
import { Controller } from "@hotwired/stimulus"
|
||||||
|
|
||||||
|
export default class extends Controller {
|
||||||
|
static targets = ["input"]
|
||||||
|
|
||||||
|
submit() {
|
||||||
|
clearTimeout(this.timeout)
|
||||||
|
this.timeout = setTimeout(() => {
|
||||||
|
this.element.requestSubmit()
|
||||||
|
}, 300)
|
||||||
|
}
|
||||||
|
}
|
@ -97,6 +97,15 @@ class City < ApplicationRecord
|
|||||||
.order("COUNT(ahoy_events.id) DESC, cities.name ASC").limit(limit)
|
.order("COUNT(ahoy_events.id) DESC, cities.name ASC").limit(limit)
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
scope :search_by_name, ->(query) {
|
||||||
|
return all if query.blank?
|
||||||
|
|
||||||
|
decoded_query = URI.decode_www_form_component(query).downcase
|
||||||
|
|
||||||
|
where(
|
||||||
|
"LOWER(cities.name) LIKE :query", query: "%#{decoded_query}%"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def to_s
|
def to_s
|
||||||
|
30
app/views/cities/_search_city.html.erb
Normal file
30
app/views/cities/_search_city.html.erb
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<div class="mt-8 max-w-2xl mx-auto">
|
||||||
|
<%= form_with url: cities_path, method: :get, class: "relative", data: { controller: "search" } do |f| %>
|
||||||
|
<div class="relative">
|
||||||
|
<div class="absolute inset-y-0 left-0 flex items-center pl-4 pointer-events-none">
|
||||||
|
<svg class="w-5 h-5 text-base-content/50" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<%= f.text_field :query,
|
||||||
|
value: params[:query] ? URI.decode_www_form_component(params[:query]) : nil,
|
||||||
|
class: "w-full pl-12 pr-4 py-3 rounded-full bg-base-200/50 backdrop-blur-sm border border-base-300 focus:outline-none focus:ring-2 focus:ring-primary/50",
|
||||||
|
placeholder: "Search cities...",
|
||||||
|
autocomplete: "off",
|
||||||
|
data: {
|
||||||
|
action: "input->search#submit",
|
||||||
|
search_target: "input"
|
||||||
|
} %>
|
||||||
|
<% if params[:query].present? %>
|
||||||
|
<%= link_to cities_path, class: "absolute inset-y-0 right-0 flex items-center pr-4" do %>
|
||||||
|
<svg class="w-5 h-5 text-base-content/50 hover:text-base-content" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||||
|
</svg>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<%= f.hidden_field :region, value: params[:region] if params[:region] %>
|
||||||
|
<%= f.hidden_field :country, value: params[:country] if params[:country] %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
@ -22,6 +22,8 @@
|
|||||||
Discover AI-generated weather art from cities around the world
|
Discover AI-generated weather art from cities around the world
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<%= render 'cities/search_city' %>
|
||||||
|
|
||||||
<!-- 特色图片信息 -->
|
<!-- 特色图片信息 -->
|
||||||
<% if featured_art %>
|
<% if featured_art %>
|
||||||
<div class="inline-block mt-6 px-4 py-2 bg-base-100/80 backdrop-blur-sm rounded-full text-sm">
|
<div class="inline-block mt-6 px-4 py-2 bg-base-100/80 backdrop-blur-sm rounded-full text-sm">
|
||||||
@ -99,7 +101,7 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-sm text-base-content/70">
|
<div class="text-sm text-base-content/70 hidden">
|
||||||
<%= @cities.count %> <%= 'city'.pluralize(@cities.count) %>
|
<%= @cities.count %> <%= 'city'.pluralize(@cities.count) %>
|
||||||
<% if @current_country %>
|
<% if @current_country %>
|
||||||
in <%= @current_country.name %>
|
in <%= @current_country.name %>
|
||||||
@ -111,11 +113,29 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="container mx-auto px-4 py-8">
|
<div class="container mx-auto px-4 py-8">
|
||||||
<div class="container mx-auto px-4 pb-16">
|
<div class="container mx-auto px-4 pb-16">
|
||||||
|
|
||||||
|
<% if @cities.empty? %>
|
||||||
|
<div class="text-center py-16">
|
||||||
|
<div class="text-base-content/50">
|
||||||
|
<svg class="w-16 h-16 mx-auto mb-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h10a2 2 0 012 2v1m2 13a2 2 0 01-2-2V7m2 13a2 2 0 002-2V9a2 2 0 00-2-2h-2m-4-3H9M7 16h6M7 8h6v4H7V8z" />
|
||||||
|
</svg>
|
||||||
|
<h3 class="text-xl font-semibold mb-2">No cities found</h3>
|
||||||
|
<p class="text-base-content/70">
|
||||||
|
Try adjusting your search or filters to find what you're looking for.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<% else %>
|
||||||
|
<!-- 现有的城市网格代码 -->
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||||
<%= render partial: 'city', collection: @cities %>
|
<%= render partial: 'city', collection: @cities %>
|
||||||
</div>
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<%= render 'shared/pagination',
|
<%= render 'shared/pagination',
|
||||||
collection: @cities,
|
collection: @cities,
|
||||||
|
Loading…
Reference in New Issue
Block a user