Skip to content

Commit 0a5c2a2

Browse files
authored
Merge pull request #159 from denis1011101/backlog/b-33-search-input
Backlog/b 33 search input
2 parents 43cbf26 + 8e788ce commit 0a5c2a2

10 files changed

Lines changed: 52 additions & 49 deletions

File tree

app/controllers/court_suggestions_controller.rb

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,7 @@ def create
2626
@proposed_court.assign_attributes(editable_params)
2727
@suggestion = @court.court_suggestions.build(
2828
user: current_user,
29-
payload: changed_payload,
30-
comment: params.dig(:court_suggestion, :comment)
29+
payload: changed_payload
3130
)
3231

3332
if @suggestion.save

app/javascript/controllers/geolocation_controller.js

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export default class extends Controller {
2121
this.isSearching = false
2222
this.searchTimeout = null
2323
this.clearResultsTimeout = null
24-
this.lastSearchResults = []
24+
this.selectableResults = []
2525

2626
// Добавляем слушатели один раз
2727
if (this.hasInputTarget) {
@@ -164,27 +164,35 @@ export default class extends Controller {
164164
if (!items?.length) {
165165
this.resultsTarget.innerHTML = ""
166166
this.resultsTarget.classList.add("hidden")
167+
this.selectableResults = []
167168
return
168169
}
169170

171+
// Служебные строки («Ищу…», «Ничего не найдено») рисуем, но выбирать их нельзя:
172+
// data-idx считаем по выбираемым элементам, их же и запоминаем.
173+
const selectable = []
174+
170175
this.resultsTarget.innerHTML = items
171-
.map((item, idx) =>
172-
item.class === "muted"
173-
? `<div class="p-3 text-sm text-gray-500 text-center">${escapeHtml(item.label)}</div>`
174-
: `<button type="button" data-action="click->geolocation#selectResult" data-idx="${idx}" class="w-full text-left p-3 hover:bg-gray-100 text-sm border-b last:border-b-0 transition">
176+
.map((item) => {
177+
if (!isSelectableResult(item)) {
178+
return `<div class="p-3 text-sm text-gray-500 text-center">${escapeHtml(item.label)}</div>`
179+
}
180+
181+
const idx = selectable.push(item) - 1
182+
return `<button type="button" data-action="click->geolocation#selectResult" data-idx="${idx}" class="w-full text-left p-3 hover:bg-gray-100 text-sm border-b last:border-b-0 transition">
175183
<div class="font-medium text-gray-800">${escapeHtml(item.label)}</div>
176184
</button>`
177-
)
185+
})
178186
.join("")
179187

180188
this.resultsTarget.classList.remove("hidden")
181-
this.lastSearchResults = items
189+
this.selectableResults = selectable
182190
}
183191

184192
selectResult(e) {
185193
e.preventDefault()
186194
const idx = parseInt(e.currentTarget.dataset.idx, 10)
187-
const item = this.lastSearchResults?.[idx]
195+
const item = this.selectableResults?.[idx]
188196

189197
if (!item || !this.hasInputTarget) return
190198

@@ -194,7 +202,7 @@ export default class extends Controller {
194202
}
195203

196204
selectResultByIndex(idx) {
197-
const item = this.lastSearchResults[idx]
205+
const item = this.selectableResults[idx]
198206
if (item) {
199207
this.setCoordinates(item.lat, item.lon)
200208
this.clearResults()
@@ -207,33 +215,27 @@ export default class extends Controller {
207215
if (!val || this.isCoordinates(val)) return
208216

209217
e.preventDefault()
210-
if (this.lastSearchResults.length > 0) {
218+
if (this.selectableResults.length > 0) {
211219
this.selectResultByIndex(0)
212220
} else {
213221
this.searchCity(val)
214222
}
215223
}
216224

217-
onFormSubmit(e) {
218-
if (!this.hasInputTarget) return
219-
const input = this.inputTarget.value.trim()
220-
221-
if (!input || this.isCoordinates(input)) return
222-
223-
e.preventDefault()
224-
this.lastSearchResults.length > 0 ? null : this.searchCity(input)
225-
}
226-
227225
clearResults() {
228226
this.cancelResultsClear()
229227
if (this.hasResultsTarget) {
230228
this.resultsTarget.innerHTML = ""
231229
this.resultsTarget.classList.add("hidden")
232230
}
233-
this.lastSearchResults = []
231+
this.selectableResults = []
234232
}
235233
}
236234

235+
function isSelectableResult(item) {
236+
return item?.class !== "muted" && Number.isFinite(item?.lat) && Number.isFinite(item?.lon)
237+
}
238+
237239
function escapeHtml(unsafe) {
238240
return String(unsafe)
239241
.replace(/&/g, "&amp;")

app/models/court_suggestion.rb

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class CourtSuggestion < ApplicationRecord
1212
conditions: -> { where(status: "pending") },
1313
message: :pending_exists
1414
}
15-
validate :payload_or_comment_present
15+
validate :payload_present, on: :create
1616
validate :payload_fields_are_editable
1717

1818
scope :pending, -> { where(status: "pending") }
@@ -52,8 +52,11 @@ def reject_by!(admin)
5252

5353
private
5454

55-
def payload_or_comment_present
56-
errors.add(:base, :blank_suggestion) if payload.blank? && comment.blank?
55+
# Комментарий остался только у записей, созданных до того, как поле убрали из формы,
56+
# поэтому новое предложение обязано менять хотя бы одно поле. Проверяем на создании:
57+
# иначе модератор не смог бы одобрить или отклонить давнее предложение без правок.
58+
def payload_present
59+
errors.add(:base, :blank_suggestion) if payload.blank?
5760
end
5861

5962
def payload_fields_are_editable

app/views/court_suggestions/new.html.erb

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,6 @@
1717
local: true do |form| %>
1818
<%= render "courts/fields", form: form, court: @proposed_court %>
1919

20-
<div class="mt-4">
21-
<%= label_tag "court_suggestion_comment", t("courts.suggestions.comment"), class: "block text-sm font-semibold text-gray-800 dark:text-slate-200" %>
22-
<%= text_area_tag "court_suggestion[comment]", @suggestion.comment, rows: 4,
23-
placeholder: t("courts.suggestions.comment_placeholder"),
24-
class: "mt-1 block w-full rounded-md border border-gray-300 bg-white px-3 py-2 dark:border-white/15 dark:bg-slate-700 dark:text-slate-100" %>
25-
</div>
26-
2720
<div class="mt-4 flex items-center gap-2">
2821
<%= form.submit t("courts.suggestions.submit"), class: "rounded-md bg-indigo-600 px-4 py-2 text-white hover:bg-indigo-700" %>
2922
<%= link_to t("courts.suggestions.cancel"), @court, class: "rounded-md border border-gray-300 px-3 py-2 text-sm text-gray-700 dark:border-white/10 dark:text-slate-300" %>

app/views/searches/index.html.erb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
<%= form_with url: searches_path, method: :get, local: true,
1010
data: {
1111
controller: "geolocation",
12-
action: "submit->geolocation#onFormSubmit",
1312
geolocation_unsupported_value: t("searches.index.geolocation.unsupported"),
1413
geolocation_permission_denied_value: t("searches.index.geolocation.permission_denied"),
1514
geolocation_unavailable_value: t("searches.index.geolocation.unavailable"),

config/locales/en.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ en:
3131
errors:
3232
messages:
3333
pending_exists: "already has a pending suggestion for this court"
34-
blank_suggestion: "Change at least one field or add a comment"
34+
blank_suggestion: "Change at least one field"
3535
invalid_fields: "contains fields that cannot be changed"
3636
score_recognitions:
3737
errors:
@@ -498,7 +498,6 @@ en:
498498
new_title: "Suggest a correction for %{court}"
499499
new_help: "Change only the details that are inaccurate. The current court stays published until a moderator reviews your suggestion."
500500
comment: "Comment"
501-
comment_placeholder: "Looking for one more player, any level. Balls are on me, we play 1.5 hours"
502501
submit: "Send suggestion"
503502
cancel: "Cancel"
504503
created: "Thank you. Your suggestion was sent for moderator review."

config/locales/es.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ es:
22
errors:
33
messages:
44
pending_exists: "ya tiene una propuesta pendiente para esta pista"
5-
blank_suggestion: "Cambia al menos un campo o añade un comentario"
5+
blank_suggestion: "Cambia al menos un campo"
66
invalid_fields: "contiene campos que no se pueden cambiar"
77
score_recognitions:
88
errors:
@@ -473,7 +473,6 @@ es:
473473
new_title: "Proponer una corrección para %{court}"
474474
new_help: "Cambia solo los datos incorrectos. La pista actual seguirá publicada hasta que un moderador revise la propuesta."
475475
comment: "Comentario"
476-
comment_placeholder: "Buscamos a un jugador más, cualquier nivel. Llevo las pelotas, jugamos 1,5 horas"
477476
submit: "Enviar propuesta"
478477
cancel: "Cancelar"
479478
created: "Gracias. Tu propuesta se ha enviado para revisión."

config/locales/ru.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ ru:
22
errors:
33
messages:
44
pending_exists: "уже имеет предложение для этого корта на проверке"
5-
blank_suggestion: "Измените хотя бы одно поле или добавьте комментарий"
5+
blank_suggestion: "Измените хотя бы одно поле"
66
invalid_fields: "содержит поля, которые нельзя изменять"
77
score_recognitions:
88
errors:
@@ -482,7 +482,6 @@ ru:
482482
new_title: "Предложить правку для корта «%{court}»"
483483
new_help: "Измените только неточные данные. Текущий корт останется опубликованным до проверки предложения модератором."
484484
comment: "Комментарий"
485-
comment_placeholder: "Ищем ещё одного игрока, уровень любой. Мяч свой, играем 1,5 часа"
486485
submit: "Отправить предложение"
487486
cancel: "Отмена"
488487
created: "Спасибо. Предложение отправлено на проверку модератору."

test/controllers/court_suggestions_controller_test.rb

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class CourtSuggestionsControllerTest < ActionDispatch::IntegrationTest
2626

2727
assert_response :success
2828
assert_select "input[name='court_suggestion[name]'][value='Published Court']"
29-
assert_select "textarea[name='court_suggestion[comment]']"
29+
assert_select "textarea[name='court_suggestion[comment]']", count: 0
3030
end
3131

3232
test "owner is sent to the normal edit form" do
@@ -49,22 +49,21 @@ class CourtSuggestionsControllerTest < ActionDispatch::IntegrationTest
4949

5050
suggestion = @court.court_suggestions.find_by!(user: @author)
5151
assert_equal({ "sport" => "Padel" }, suggestion.payload)
52-
assert_equal "The lines were changed.", suggestion.comment
52+
assert_nil suggestion.comment
5353
assert_equal @court.name, @court.reload.name
5454
assert_redirected_to @court
5555
assert_equal suggestion, notified.first.first
5656
end
5757

58-
test "comment-only suggestion is accepted" do
58+
test "suggestion without a single changed field is rejected" do
5959
sign_in_as(@author)
6060

6161
post court_corrections_url(@court), params: {
6262
court_suggestion: { name: @court.name, sport: @court.sport, comment: "Opening hours are wrong." }
6363
}
6464

65-
suggestion = @court.court_suggestions.find_by!(user: @author)
66-
assert_empty suggestion.payload
67-
assert_redirected_to @court
65+
assert_response :unprocessable_entity
66+
assert_nil @court.court_suggestions.find_by(user: @author)
6867
end
6968

7069
test "second pending suggestion is rejected" do

test/models/court_suggestion_test.rb

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,27 @@
11
require "test_helper"
22

33
class CourtSuggestionTest < ActiveSupport::TestCase
4-
test "requires a changed field or comment" do
4+
test "requires a changed field" do
55
suggestion = CourtSuggestion.new(court: courts(:one), user: users(:one), payload: {})
66

77
assert_not suggestion.valid?
8+
assert_includes suggestion.errors[:base], I18n.t("errors.messages.blank_suggestion")
89
end
910

10-
test "allows comment-only suggestion" do
11+
test "a comment alone no longer makes a suggestion" do
1112
suggestion = CourtSuggestion.new(court: courts(:one), user: users(:one), payload: {}, comment: "The opening hours are outdated")
1213

13-
assert suggestion.valid?
14+
assert_not suggestion.valid?
15+
end
16+
17+
test "a comment-only suggestion saved before the field went away can still be reviewed" do
18+
admin = users(:one)
19+
admin.update_column(:admin, true)
20+
suggestion = CourtSuggestion.new(court: courts(:two), user: users(:two), payload: {}, comment: "The opening hours are outdated")
21+
suggestion.save!(validate: false)
22+
23+
assert suggestion.reject_by!(admin)
24+
assert_equal "rejected", suggestion.reload.status
1425
end
1526

1627
test "allows only one pending suggestion per user and court" do

0 commit comments

Comments
 (0)