diff options
author | Zarino Zappia <mail@zarino.co.uk> | 2019-02-15 18:12:58 +0000 |
---|---|---|
committer | Matthew Somerville <matthew-github@dracos.co.uk> | 2019-02-25 12:19:50 +0000 |
commit | 257b6a53187c4200aa26e13d18a1be569cab2088 (patch) | |
tree | f3bab52058a53f1e4fd050fe002e53d9f0ab98f0 | |
parent | 0c8aae1ab17617f46f400bab81bb7a3b144b4a1e (diff) |
Duplicate suggestion UI when reporting problems.
The previously staff-only fixmystreet.set_up.manage_duplicates() has
been promoted from staff.js to its own file so that we can use it to
display the duplicate suggestions on the `/report/new` form.
render_duplicate_list (the old refresh_duplicate_list) no longer filters
or slices the reports_list returned by the ajax call, since it turns out
the server handles all of that (`sub _nearby_json` in `Report.pm` and
`sub nearby` in `Nearby.pm`).
Since the expandable list items include the "fancybox" image previews,
the fancybox CSS now has to be included in the `/report/new` page head,
hence the addition of "photo-js.html" in `fill_in_details.html`.
The “Get updates” flow reuses the same JavaScript that handles the
submission of the "Get updates" drawer at the bottom of around pages.
-rw-r--r-- | CHANGELOG.md | 3 | ||||
-rw-r--r-- | templates/web/base/alert/list-ajax.html | 2 | ||||
-rw-r--r-- | templates/web/base/alert/list.html | 2 | ||||
-rwxr-xr-x | templates/web/base/around/display_location.html | 1 | ||||
-rw-r--r-- | templates/web/base/js/translation_strings.html | 3 | ||||
-rw-r--r-- | templates/web/base/report/_inspect.html | 7 | ||||
-rw-r--r-- | templates/web/base/report/nearby.html | 2 | ||||
-rw-r--r-- | templates/web/base/report/new/duplicate_suggestions.html | 42 | ||||
-rw-r--r-- | templates/web/base/report/new/fill_in_details.html | 2 | ||||
-rw-r--r-- | templates/web/base/report/new/form_report.html | 1 | ||||
-rw-r--r-- | web/cobrands/fixmystreet/fixmystreet.js | 2 | ||||
-rw-r--r-- | web/cobrands/fixmystreet/staff.js | 80 | ||||
-rw-r--r-- | web/cobrands/sass/_base.scss | 71 | ||||
-rw-r--r-- | web/js/duplicates.js | 206 | ||||
-rw-r--r-- | web/js/map-OpenLayers.js | 8 |
15 files changed, 338 insertions, 94 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index c7d5d4bb6..abe507e60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ ## Releases * Unreleased + - New features: + - (Optional) auto-suggestion of similar nearby problems, + while reporting, to discourage duplicate reports. #2386 - Front end improvements: - Track map state in URL to make sharing links easier. #2242 - Admin improvements: diff --git a/templates/web/base/alert/list-ajax.html b/templates/web/base/alert/list-ajax.html index 639af6f07..5da71b58c 100644 --- a/templates/web/base/alert/list-ajax.html +++ b/templates/web/base/alert/list-ajax.html @@ -4,6 +4,6 @@ %] [% END %] -<div id="alerts"> +<div id="alerts" class="js-alert-list"> [% INCLUDE 'alert/_list.html' %] </div> diff --git a/templates/web/base/alert/list.html b/templates/web/base/alert/list.html index 385cd7d32..14215b65d 100644 --- a/templates/web/base/alert/list.html +++ b/templates/web/base/alert/list.html @@ -32,7 +32,7 @@ </div> [% END %] -<form id="alerts" name="alerts" method="post" action="/alert/subscribe"> +<form id="alerts" class="js-alert-list" name="alerts" method="post" action="/alert/subscribe"> [% INCLUDE 'alert/_list.html' %] diff --git a/templates/web/base/around/display_location.html b/templates/web/base/around/display_location.html index 826e70632..6c71ad631 100755 --- a/templates/web/base/around/display_location.html +++ b/templates/web/base/around/display_location.html @@ -33,6 +33,7 @@ SET bodyclass = 'mappage'; SET rss = [ tprintf(loc('Recent local problems, %s', "%s is the site name"), site_name), rss_url ]; + SET extra_js = []; INCLUDE 'header.html', title => loc('Viewing a location') robots => 'noindex,nofollow'; diff --git a/templates/web/base/js/translation_strings.html b/templates/web/base/js/translation_strings.html index af3073f91..9747773d9 100644 --- a/templates/web/base/js/translation_strings.html +++ b/templates/web/base/js/translation_strings.html @@ -41,6 +41,9 @@ fixmystreet.password_minimum_length = [% c.cobrand.password_minimum_length %]; how_to_send: '[% loc('How to send successful reports') | replace("'", "\\'") %]', more_details: '[% loc('Details') | replace("'", "\\'") %]', + this_report: '[% loc('This report') | replace("'", "\\'") %]', + this_is_the_problem: '[% loc('This is the problem') | replace("'", "\\'") %]', + or: '[% loc(' or ') | replace("'", "\\'") %]', geolocation_declined: '[% loc('You declined; please fill in the box above') | replace("'", "\\'") %]', diff --git a/templates/web/base/report/_inspect.html b/templates/web/base/report/_inspect.html index 76555cef8..fa79d9912 100644 --- a/templates/web/base/report/_inspect.html +++ b/templates/web/base/report/_inspect.html @@ -1,3 +1,6 @@ +[% extra_js = [ + version('/js/duplicates.js'), +] -%] [% permissions = c.user.permissions(problem) %] [% second_column = BLOCK -%] <div id="side-inspect"> @@ -120,7 +123,7 @@ <p class="[% "hidden" IF problem.duplicate_of %]">[% loc('Which report is it a duplicate of?') %]</p> <ul class="item-list item-list--inspect-duplicates"> [% IF problem.duplicate_of %] - [% INCLUDE 'report/_item.html' item_extra_class = 'item-list__item--selected' problem = problem.duplicate_of %] + [% INCLUDE 'report/_item_expandable.html' item_extra_class = 'item-list__item--selected' problem = problem.duplicate_of %] <li class="item-list__item"><a class="btn" href="#" id="js-change-duplicate-report">[% loc('Choose another') %]</a></li> [% END %] </ul> @@ -129,7 +132,7 @@ <p><strong>[% loc('Duplicates') %]</strong></p> <ul class="item-list item-list--inspect-duplicates"> [% FOR duplicate IN problem.duplicates %] - [% INCLUDE 'report/_item.html' problem = duplicate %] + [% INCLUDE 'report/_item_expandable.html' problem = duplicate %] [% END %] </ul> [% END %] diff --git a/templates/web/base/report/nearby.html b/templates/web/base/report/nearby.html index c64b10d7f..1e0d6cc79 100644 --- a/templates/web/base/report/nearby.html +++ b/templates/web/base/report/nearby.html @@ -1,3 +1,3 @@ [%~ FOREACH problem IN reports ~%] - [%~ INCLUDE 'reports/_list-entry.html' ~%] + [%~ INCLUDE 'report/_item_expandable.html' ~%] [%~ END ~%] diff --git a/templates/web/base/report/new/duplicate_suggestions.html b/templates/web/base/report/new/duplicate_suggestions.html new file mode 100644 index 000000000..582ba58e9 --- /dev/null +++ b/templates/web/base/report/new/duplicate_suggestions.html @@ -0,0 +1,42 @@ +[% IF c.cobrand.suggest_duplicates %] +[% extra_js.push( + version('/js/duplicates.js'), +) -%] +<div id="js-duplicate-reports" class="duplicate-report-suggestions hidden"> + <button class="duplicate-report-suggestions__close js-hide-duplicate-suggestions">[% loc('Close') %]</button> + <h2 class="form-section-heading">[% loc('Already been reported?') %]</h2> + <div class="form-section-description"> + [% IF c.cobrand.is_council %] + <p>[% loc('There are similar problems nearby that we’re already aware of, is one of them yours?') %]</p> + [% ELSE %] + <p>[% loc('We’ve already reported these nearby problems to the council. Is one of them yours?') %]</p> + [% END %] + </div> + + <ul class="item-list"></ul> + <button class="btn btn--block js-hide-duplicate-suggestions">[% loc('Continue – report a new problem') %]</button> +</div> +<div class="js-template-get-updates hidden"> + <div id="alerts" class="get-updates js-alert-list"> + <p id="rznvy_hint"> + [% IF c.user_exists %] + [% loc('Would you like us to notify you when this problem is updated or fixed?') %] + [% ELSE %] + [% loc('If you let us know your email address, we’ll notify you when this problem is updated or fixed.') %] + [% END %] + </p> + <input type="hidden" name="id" disabled> + <input type="hidden" name="token" value="[% csrf_token %]" disabled> + <input type="hidden" name="type" value="updates" disabled> + [% IF c.user_exists %] + <input type="submit" value="[% loc('Get updates') %]" class="btn btn--block" id="alert_email_button"> + [% ELSE %] + <label for="rznvy_input">[% loc('Your email') %]</label> + <div class="form-txt-submit-box"> + <input type="email" class="form-control" name="rznvy" id="rznvy_input" aria-described-by="rznvy_hint" disabled> + <input type="submit" value="[% loc('Get updates') %]" class="btn" id="alert_email_button"> + </div> + [% END %] + </div> +</div> +[% END %] diff --git a/templates/web/base/report/new/fill_in_details.html b/templates/web/base/report/new/fill_in_details.html index 8fa1253da..fa7aabce3 100644 --- a/templates/web/base/report/new/fill_in_details.html +++ b/templates/web/base/report/new/fill_in_details.html @@ -4,6 +4,8 @@ SET bodyclass = ''; SET bodyclass = 'mappage'; SET bodyclass = bodyclass _ " with-notes" IF sidebar_html; + SET extra_js = []; + PROCESS "report/photo-js.html"; PROCESS "maps/${map.type}.html" IF report.used_map; INCLUDE 'header.html', title => loc('Reporting a problem'); %] diff --git a/templates/web/base/report/new/form_report.html b/templates/web/base/report/new/form_report.html index 39e29c723..a5b378641 100644 --- a/templates/web/base/report/new/form_report.html +++ b/templates/web/base/report/new/form_report.html @@ -7,6 +7,7 @@ [% PROCESS "report/new/category_wrapper.html" %] [% TRY %][% PROCESS 'report/new/after_category.html' %][% CATCH file %][% END %] +[% PROCESS "report/new/duplicate_suggestions.html" %] <div class="js-hide-if-invalid-category"> [% TRY %][% PROCESS 'report/new/_form_labels.html' %][% CATCH file %][% END %] diff --git a/web/cobrands/fixmystreet/fixmystreet.js b/web/cobrands/fixmystreet/fixmystreet.js index bbcf73055..d6d03eac3 100644 --- a/web/cobrands/fixmystreet/fixmystreet.js +++ b/web/cobrands/fixmystreet/fixmystreet.js @@ -974,7 +974,7 @@ $.extend(fixmystreet.set_up, { e.preventDefault(); var form = $('<form/>').attr({ method:'post', action:"/alert/subscribe" }); form.append($('<input name="alert" value="Subscribe me to an email alert" type="hidden" />')); - $('#alerts input[type=text], #alerts input[type=hidden], #alerts input[type=radio]:checked').each(function() { + $(this).closest('.js-alert-list').find('input[type=text], input[type=hidden], input[type=radio]:checked').each(function() { var $v = $(this); $('<input/>').attr({ name:$v.attr('name'), value:$v.val(), type:'hidden' }).appendTo(form); }); diff --git a/web/cobrands/fixmystreet/staff.js b/web/cobrands/fixmystreet/staff.js index fadf18356..a7e0c8896 100644 --- a/web/cobrands/fixmystreet/staff.js +++ b/web/cobrands/fixmystreet/staff.js @@ -1,84 +1,4 @@ fixmystreet.staff_set_up = { - manage_duplicates: function() { - // Deal with changes to report state by inspector/other staff, specifically - // displaying nearby reports if it's changed to 'duplicate'. - function refresh_duplicate_list() { - var report_id = $("#report_inspect_form .js-report-id").text(); - var args = { - filter_category: $("#report_inspect_form select#category").val(), - latitude: $('input[name="latitude"]').val(), - longitude: $('input[name="longitude"]').val() - }; - $("#js-duplicate-reports ul").html('<li class="item-list__item">Loading...</li>'); - var nearby_url = '/report/'+report_id+'/nearby.json'; - $.getJSON(nearby_url, args, function(data) { - var duplicate_of = $("#report_inspect_form [name=duplicate_of]").val(); - var $reports = $(data.reports_list) - .not("[data-report-id="+report_id+"]") - .slice(0, 5); - $reports.filter("[data-report-id="+duplicate_of+"]").addClass("item-list--reports__item--selected"); - - (function() { - var timeout; - $reports.on('mouseenter', function(){ - clearTimeout(timeout); - fixmystreet.maps.markers_highlight(parseInt($(this).data('reportId'), 10)); - }).on('mouseleave', function(){ - timeout = setTimeout(fixmystreet.maps.markers_highlight, 50); - }); - })(); - - $("#js-duplicate-reports ul").empty().prepend($reports); - - $reports.find("a").click(function() { - var report_id = $(this).closest("li").data('reportId'); - $("#report_inspect_form [name=duplicate_of]").val(report_id); - $("#js-duplicate-reports ul li").removeClass("item-list--reports__item--selected"); - $(this).closest("li").addClass("item-list--reports__item--selected"); - return false; - }); - - show_nearby_pins(data, report_id); - }); - } - - function show_nearby_pins(data, report_id) { - var markers = fixmystreet.maps.markers_list( data.pins, true ); - // We're replacing all the features in the markers layer with the - // possible duplicates, but the list of pins from the server doesn't - // include the current report. So we need to extract the feature for - // the current report and include it in the list of features we're - // showing on the layer. - var report_marker = fixmystreet.maps.get_marker_by_id(parseInt(report_id, 10)); - if (report_marker) { - markers.unshift(report_marker); - } - fixmystreet.markers.removeAllFeatures(); - fixmystreet.markers.addFeatures( markers ); - } - - function state_change() { - // The duplicate report list only makes sense when state is 'duplicate' - if ($(this).val() !== "duplicate") { - $("#js-duplicate-reports").addClass("hidden"); - return; - } else { - $("#js-duplicate-reports").removeClass("hidden"); - } - // If this report is already marked as a duplicate of another, then - // there's no need to refresh the list of duplicate reports - var duplicate_of = $("#report_inspect_form [name=duplicate_of]").val(); - if (!!duplicate_of) { - return; - } - - refresh_duplicate_list(); - } - - $("#report_inspect_form").on("change.state", "select#state", state_change); - $("#js-change-duplicate-report").click(refresh_duplicate_list); - }, - action_scheduled_raise_defect: function() { $("#report_inspect_form").find('[name=state]').on('change', function() { if ($(this).val() !== "action scheduled") { diff --git a/web/cobrands/sass/_base.scss b/web/cobrands/sass/_base.scss index 6fc63d9f0..c3cf3cb1b 100644 --- a/web/cobrands/sass/_base.scss +++ b/web/cobrands/sass/_base.scss @@ -530,6 +530,53 @@ ul.error { @include border-radius(0.25em); } +.duplicate-report-suggestions { + position: relative; + + .item-list__item--expandable { + border-top: 1px solid #ddd; + } + + .item-list { + margin: 0 -1em 1em -1em; + border-bottom: 1px solid #ddd; + } +} + +.duplicate-report-suggestions__close { + position: absolute; + right: 0; + top: 0; + display: block; + width: 32px; + height: 0; + padding-top: 32px; + overflow: hidden; + border: none; + background: transparent; + opacity: 0.5; + line-height: 2em; // Make sure line box is taller than text, so text is pushed below hidden overflow. + + &:hover, + &:focus { + opacity: 0.7; + } + + // Doing some gymnastics so we can reuse the existing .btn--close icon. + &:before { + content: ""; + display: block; + width: 16px; + height: 16px; + position: absolute; + top: 8px; + left: 8px; + background-repeat: no-repeat; + background-size: 112px 16px; + @include svg-background-image('/cobrands/fixmystreet/images/button-icons'); + background-position: -32px 0; + } +} /*** LAYOUT ***/ @@ -1256,17 +1303,23 @@ input.final-submit { .item-list__item--expandable { @include clearfix(); - border-top: 1px solid #fff; - background-color: transparent; margin: 0; padding: 1em; .js & { cursor: pointer; - &:hover, &.hovered { + &:hover, + &.hovered { background-color: $itemlist_item_background_hover; } + + &.item-list__item--selected, + &.item-list__item--selected:hover, + &.item-list__item--selected.hovered { + cursor: default; + background-color: #fff; + } } h3 { @@ -1296,7 +1349,7 @@ input.final-submit { cursor: default; &:hover { - background-color: transparent; + background-color: $itemlist_item_background; } } } @@ -1324,6 +1377,16 @@ input.final-submit { } } +.item-list__item .get-updates { + margin: 0 -1em -1em -1em; + padding: 1em; + background-color: $itemlist_item_background_hover; + + p, label { + margin: 0 0 0.5em 0; + } +} + .problem-header .update-img, .item-list .update-img { float: $right; diff --git a/web/js/duplicates.js b/web/js/duplicates.js new file mode 100644 index 000000000..4ed54846c --- /dev/null +++ b/web/js/duplicates.js @@ -0,0 +1,206 @@ +$(function() { + + // Store a reference to the "duplicate" report pins so we can + // quickly remove them when we’re finished showing duplicates. + var current_duplicate_markers; + + // Report ID will be available on report inspect page, + // but undefined on new report page. + var report_id = $("#report_inspect_form .js-report-id").text() || undefined; + + function refresh_duplicate_list() { + // This function will return a jQuery Promise, so callbacks can be + // hooked onto it, once the ajax request as completed. + var dfd = $.Deferred(); + + var nearby_url; + var url_params = { + filter_category: $('select[name="category"]').val(), + latitude: $('input[name="latitude"]').val(), + longitude: $('input[name="longitude"]').val() + }; + + if ( report_id ) { + nearby_url = '/report/' + report_id + '/nearby.json'; + url_params.distance = 1000; // Inspectors might want to see reports fairly far away (1000 metres) + url_params.pin_size = 'small'; // How it's always been + } else { + nearby_url = '/around/nearby'; + url_params.distance = 250; // Only want to bother public with very nearby reports (250 metres) + url_params.pin_size = 'normal'; + } + + $.ajax({ + url: nearby_url, + data: url_params, + dataType: 'json' + }).done(function(response) { + if ( response.pins.length ){ + render_duplicate_list(response); + render_duplicate_pins(response); + } else { + remove_duplicate_pins(); + remove_duplicate_list(); + } + dfd.resolve(); + }).fail(function(){ + remove_duplicate_pins(); + remove_duplicate_list(); + dfd.reject(); + }); + + return dfd.promise(); + } + + function render_duplicate_list(api_response) { + var $reports = $( api_response.reports_list ); + + var duplicate_of = $('#report_inspect_form [name="duplicate_of"]').val(); + if ( duplicate_of ) { + $reports.filter('[data-report-id="' + duplicate_of + '"]') + .addClass("item-list__item--selected"); + } + + $("#js-duplicate-reports ul").empty().prepend( $reports ); + fixmystreet.set_up.fancybox_images(); + + $('#js-duplicate-reports').hide().removeClass('hidden').slideDown(); + if ( $('#problem_form').length ) { + $('.js-hide-if-invalid-category').slideUp(); + } + + // Highlight map pin when hovering associated list item. + var timeout; + $reports.on('mouseenter', function(){ + var id = parseInt( $(this).data('reportId'), 10 ); + clearTimeout( timeout ); + fixmystreet.maps.markers_highlight( id ); + }).on('mouseleave', function(){ + timeout = setTimeout( fixmystreet.maps.markers_highlight, 50 ); + }); + + // Add a "select this report" button, when on the report inspect form. + if ( $('#report_inspect_form').length ) { + $reports.each(function(){ + var $button = $('<button>').addClass('btn btn--small btn--primary'); + $button.text(translation_strings.this_report); + $button.on('click', function(e) { + e.preventDefault(); // Prevent button from submitting parent form + var report_id = $(this).closest('li').data('reportId'); + $('#report_inspect_form [name="duplicate_of"]').val(report_id); + $(this).closest('li') + .addClass('item-list__item--selected') + .siblings('.item-list__item--selected') + .removeClass('item-list__item--selected'); + }); + $(this).find('.item-list__item--expandable__actions').append($button); + }); + } + + // Add a "track this report" button when on the regular reporting form. + if ( $('#problem_form').length ) { + $reports.each(function() { + var $li = $(this); + var id = parseInt( $li.data('reportId'), 10 ); + var alert_url = '/alert/subscribe?id=' + encodeURIComponent(id); + var $button = $('<a>').addClass('btn btn--small btn--primary'); + $button.text(translation_strings.this_is_the_problem); + $button.attr('href', alert_url); + $button.on('click', function(e){ + e.preventDefault(); + var $div = $('.js-template-get-updates > div').clone(); + $div.find('input[name="id"]').val(id); + $div.find('input[disabled]').prop('disabled', false); + $div.hide().appendTo($li).slideDown(250, function(){ + $div.find('input[type="email"]').focus(); + }); + $li.find('.item-list__item--expandable__actions').slideUp(250); + $li.removeClass('js-expandable'); + $li.addClass('item-list__item--selected'); + }); + $li.find('.item-list__item--expandable__actions').append($button); + }); + } + } + + function render_duplicate_pins(api_response) { + var markers = fixmystreet.maps.markers_list( api_response.pins, true ); + fixmystreet.markers.removeFeatures( current_duplicate_markers ); + fixmystreet.markers.addFeatures( markers ); + current_duplicate_markers = markers; + } + + function remove_duplicate_list(cb) { + var animations = []; + + animations.push( $.Deferred() ); + $('#js-duplicate-reports').slideUp(function(){ + $(this).addClass('hidden'); + $(this).find('ul').empty(); + animations[0].resolve(); + }); + if ( $('#problem_form').length ) { + animations.push( $.Deferred() ); + $('.js-hide-if-invalid-category').slideDown(function(){ + animations[1].resolve(); + }); + } + + $.when.apply(this, animations).then(cb); + } + + function remove_duplicate_pins() { + fixmystreet.markers.removeFeatures( current_duplicate_markers ); + } + + function inspect_form_state_change() { + // The duplicate report list only makes sense when state is 'duplicate' + if ($(this).val() !== "duplicate") { + $("#js-duplicate-reports").addClass("hidden"); + return; + } else { + $("#js-duplicate-reports").removeClass("hidden"); + } + // If this report is already marked as a duplicate of another, then + // there's no need to refresh the list of duplicate reports + var duplicate_of = $("#report_inspect_form [name=duplicate_of]").val(); + if (!!duplicate_of) { + return; + } + refresh_duplicate_list(); + } + + var category_changing = false; + function problem_form_category_change() { + // Annoyingly this event seems to fire a few times in quick succession, + // so set a flag to avoid multiple overlapping refreshes. + if (category_changing) { return; } + category_changing = true; + + refresh_duplicate_list().always(function(){ + // Wait an extra second until we allow another reload. + setTimeout(function(){ + category_changing = false; + }, 1000); + }); + } + + // Want to show potential duplicates when a regular user starts a new + // report, or changes the category/location of a partial report. + $("#problem_form").on("change.category", "select#form_category", problem_form_category_change); + + // Want to show duplicates when an inspector sets a report’s state to "duplicate". + $("#report_inspect_form").on("change.state", "select#state", inspect_form_state_change); + + // Also want to give inspectors a way to select a *new* duplicate report. + $("#js-change-duplicate-report").click(refresh_duplicate_list); + + $('.js-hide-duplicate-suggestions').on('click', function(e){ + e.preventDefault(); + remove_duplicate_pins(); + remove_duplicate_list(function(){ + $('#form_title').focus(); + }); + }); + +}); diff --git a/web/js/map-OpenLayers.js b/web/js/map-OpenLayers.js index fdfeed314..984f4b098 100644 --- a/web/js/map-OpenLayers.js +++ b/web/js/map-OpenLayers.js @@ -381,15 +381,15 @@ $.extend(fixmystreet.utils, { function sidebar_highlight(problem_id) { if (typeof problem_id !== 'undefined') { - var $a = $('.item-list--reports a[href$="/' + problem_id + '"]'); - $a.parent().addClass('hovered'); + var $li = $('[data-report-id="' + problem_id + '"]'); + $li.addClass('hovered'); } else { - $('.item-list--reports .hovered').removeClass('hovered'); + $('.item-list .hovered').removeClass('hovered'); } } function marker_click(problem_id, evt) { - var $a = $('.item-list--reports a[href$="/' + problem_id + '"]'); + var $a = $('.item-list a[href$="/' + problem_id + '"]'); if (!$a[0]) { return; } |