diff options
37 files changed, 522 insertions, 75 deletions
diff --git a/perllib/FixMyStreet/App/Controller/Around.pm b/perllib/FixMyStreet/App/Controller/Around.pm index 413af814f..5bfe1ada2 100644 --- a/perllib/FixMyStreet/App/Controller/Around.pm +++ b/perllib/FixMyStreet/App/Controller/Around.pm @@ -165,10 +165,15 @@ sub display_location : Private { $c->stash->{all_pins} = $all_pins; my $interval = $all_pins ? undef : $c->cobrand->on_map_default_max_pin_age; + $c->forward( '/reports/stash_report_filter_status' ); + + # Check the category to filter by, if any, is valid + $c->forward('check_and_stash_category'); + # get the map features my ( $on_map_all, $on_map, $around_map, $distance ) = FixMyStreet::Map::map_features( $c, $latitude, $longitude, - $interval ); + $interval, $c->stash->{filter_category}, $c->stash->{filter_problem_states} ); # copy the found reports to the stash $c->stash->{on_map} = $on_map; @@ -221,6 +226,45 @@ sub check_location_is_acceptable : Private { return $c->forward('/council/load_and_check_areas'); } +=head2 check_and_stash_category + +Check that the 'filter_category' query param is valid, if it's present. Stores +the validated string in the stash as filter_category. +Puts all the valid categories in filter_categories on the stash. + +=cut + +sub check_and_stash_category : Private { + my ( $self, $c ) = @_; + + my $all_areas = $c->stash->{all_areas}; + my @bodies = $c->model('DB::Body')->search( + { 'body_areas.area_id' => [ keys %$all_areas ], deleted => 0 }, + { join => 'body_areas' } + )->all; + my %bodies = map { $_->id => $_ } @bodies; + + my @contacts = $c->model('DB::Contact')->not_deleted->search( + { + body_id => [ keys %bodies ], + }, + { + columns => [ 'category' ], + order_by => [ 'category' ], + distinct => 1 + } + )->all; + my @categories = map { $_->category } @contacts; + $c->stash->{filter_categories} = \@categories; + + + my $category = $c->req->param('filter_category'); + my %categories_mapped = map { $_ => 1 } @categories; + if ( defined $category && $categories_mapped{$category} ) { + $c->stash->{filter_category} = $category; + } +} + =head2 /ajax Handle the ajax calls that the map makes when it is dragged. The info returned diff --git a/perllib/FixMyStreet/App/Controller/My.pm b/perllib/FixMyStreet/App/Controller/My.pm index bbef1f8d8..b3d341a68 100644 --- a/perllib/FixMyStreet/App/Controller/My.pm +++ b/perllib/FixMyStreet/App/Controller/My.pm @@ -28,17 +28,26 @@ sub my : Path : Args(0) { my $p_page = $c->req->params->{p} || 1; my $u_page = $c->req->params->{u} || 1; + $c->forward( '/reports/stash_report_filter_status' ); + my $pins = []; my $problems = {}; + my $states = $c->stash->{filter_problem_states}; my $params = { - state => [ FixMyStreet::DB::Result::Problem->visible_states() ], + state => [ keys %$states ], }; $params = { %{ $c->cobrand->problems_clause }, %$params } if $c->cobrand->problems_clause; + my $category = $c->req->param('filter_category'); + if ( $category ) { + $params->{category} = $category; + $c->stash->{filter_category} = $category; + } + my $rs = $c->user->problems->search( $params, { order_by => { -desc => 'confirmed' }, rows => 50 @@ -55,6 +64,7 @@ sub my : Path : Args(0) { }; my $state = $problem->is_fixed ? 'fixed' : $problem->is_closed ? 'closed' : 'confirmed'; push @{ $problems->{$state} }, $problem; + push @{ $problems->{all} }, $problem; } $c->stash->{problems_pager} = $rs->pager; $c->stash->{problems} = $problems; @@ -71,6 +81,14 @@ sub my : Path : Args(0) { $c->stash->{updates} = \@updates; $c->stash->{updates_pager} = $rs->pager; + my @categories = $c->user->problems->search( undef, { + columns => [ 'category' ], + distinct => 1, + order_by => [ 'category' ], + } )->all; + @categories = map { $_->category } @categories; + $c->stash->{filter_categories} = \@categories; + $c->stash->{page} = 'my'; FixMyStreet::Map::display_map( $c, diff --git a/perllib/FixMyStreet/App/Controller/Reports.pm b/perllib/FixMyStreet/App/Controller/Reports.pm index 352c47da8..5a044c9af 100644 --- a/perllib/FixMyStreet/App/Controller/Reports.pm +++ b/perllib/FixMyStreet/App/Controller/Reports.pm @@ -109,6 +109,7 @@ sub ward : Path : Args(2) { $c->forward( 'ward_check', [ $ward ] ) if $ward; $c->forward( 'check_canonical_url', [ $body ] ); + $c->forward( 'stash_report_filter_status' ); $c->forward( 'load_and_group_problems' ); my $body_short = $c->cobrand->short_name( $c->stash->{body} ); @@ -120,6 +121,15 @@ sub ward : Path : Args(2) { $c->stash->{stats} = $c->cobrand->get_report_stats(); + my @categories = $c->stash->{body}->contacts->search( undef, { + columns => [ 'category' ], + distinct => 1, + order_by => [ 'category' ], + } )->all; + @categories = map { $_->category } @categories; + $c->stash->{filter_categories} = \@categories; + $c->stash->{filter_category} = $c->req->param('filter_category'); + my $pins = $c->stash->{pins}; $c->stash->{page} = 'reports'; # So the map knows to make clickable pins @@ -374,12 +384,14 @@ sub load_and_group_problems : Private { my ( $self, $c ) = @_; my $page = $c->req->params->{p} || 1; + # NB: If 't' is specified, it will override 'status'. my $type = $c->req->params->{t} || 'all'; - my $category = $c->req->params->{c} || ''; + my $category = $c->req->params->{c} || $c->req->params->{filter_category} || ''; + my $states = $c->stash->{filter_problem_states}; my $where = { non_public => 0, - state => [ FixMyStreet::DB::Result::Problem->visible_states() ] + state => [ keys %$states ] }; my $not_open = [ FixMyStreet::DB::Result::Problem::fixed_states(), FixMyStreet::DB::Result::Problem::closed_states() ]; @@ -430,7 +442,7 @@ sub load_and_group_problems : Private { my $problems = $c->cobrand->problems->search( $where, { - order_by => { -desc => 'lastupdate' }, + order_by => $c->cobrand->reports_ordering, rows => $c->cobrand->reports_per_page, } )->page( $page ); @@ -485,6 +497,26 @@ sub redirect_body : Private { $c->res->redirect( $c->uri_for($url, $c->req->params ) ); } +sub stash_report_filter_status : Private { + my ( $self, $c ) = @_; + + my $status = $c->req->param('status') || $c->cobrand->on_map_default_status; + if ( $status eq 'all' ) { + $c->stash->{filter_status} = 'all'; + $c->stash->{filter_problem_states} = FixMyStreet::DB::Result::Problem->visible_states(); + } elsif ( $status eq 'open' ) { + $c->stash->{filter_status} = 'open'; + $c->stash->{filter_problem_states} = FixMyStreet::DB::Result::Problem->open_states(); + } elsif ( $status eq 'fixed' ) { + $c->stash->{filter_status} = 'fixed'; + $c->stash->{filter_problem_states} = FixMyStreet::DB::Result::Problem->fixed_states(); + } else { + $c->stash->{filter_status} = $c->cobrand->on_map_default_status; + } + + return 1; +} + sub add_row { my ( $c, $problem, $body, $problems, $pins ) = @_; push @{$problems->{$body}}, $problem; diff --git a/perllib/FixMyStreet/Cobrand/Default.pm b/perllib/FixMyStreet/Cobrand/Default.pm index abf5d4fb5..9f44ca758 100644 --- a/perllib/FixMyStreet/Cobrand/Default.pm +++ b/perllib/FixMyStreet/Cobrand/Default.pm @@ -340,6 +340,16 @@ sub reports_per_page { return FixMyStreet->config('ALL_REPORTS_PER_PAGE') || 100; } +=head2 reports_ordering + +The order_by clause to use for reports on all reports page + +=cut + +sub reports_ordering { + return { -desc => 'lastupdate' }; +} + =head2 on_map_list_limit Return the maximum number of items to be given in the list of reports on the map @@ -356,6 +366,14 @@ Return the default maximum age for pins. sub on_map_default_max_pin_age { return '6 months'; } +=head2 on_map_default_status + +Return the default ?status= query parameter to use for filter on map page. + +=cut + +sub on_map_default_status { return 'all'; } + =head2 allow_photo_upload Return a boolean indicating whether the cobrand allows photo uploads diff --git a/perllib/FixMyStreet/DB/Result/Problem.pm b/perllib/FixMyStreet/DB/Result/Problem.pm index bee2e9bce..e55c26cd8 100644 --- a/perllib/FixMyStreet/DB/Result/Problem.pm +++ b/perllib/FixMyStreet/DB/Result/Problem.pm @@ -687,14 +687,14 @@ sub processed_summary_string { } if ($problem->can_display_external_id) { if ($duration_clause) { - $external_ref_clause = sprintf(_('council ref: %s'), $problem->external_id); + $external_ref_clause = '<strong>' . sprintf(_('Council ref: %s'), $problem->external_id) . '.</strong>'; } else { - $external_ref_clause = sprintf(_('%s ref: %s'), $problem->external_body, $problem->external_id); + $external_ref_clause = '<strong>' . sprintf(_('%s ref: %s'), $problem->external_body, $problem->external_id) . '.</strong>'; } } if ($duration_clause and $external_ref_clause) { - return "$duration_clause, $external_ref_clause" - } else { + return "$duration_clause. $external_ref_clause" + } else { return $duration_clause || $external_ref_clause } } diff --git a/perllib/FixMyStreet/DB/ResultSet/Nearby.pm b/perllib/FixMyStreet/DB/ResultSet/Nearby.pm index 91c44d5f4..a0ccb8a6d 100644 --- a/perllib/FixMyStreet/DB/ResultSet/Nearby.pm +++ b/perllib/FixMyStreet/DB/ResultSet/Nearby.pm @@ -5,11 +5,15 @@ use strict; use warnings; sub nearby { - my ( $rs, $c, $dist, $ids, $limit, $mid_lat, $mid_lon, $interval ) = @_; + my ( $rs, $c, $dist, $ids, $limit, $mid_lat, $mid_lon, $interval, $category, $states ) = @_; + + unless ( $states ) { + $states = FixMyStreet::DB::Result::Problem->visible_states(); + } my $params = { non_public => 0, - state => [ FixMyStreet::DB::Result::Problem::visible_states() ], + state => [ keys %$states ], }; $params->{'current_timestamp-lastupdate'} = { '<', \"'$interval'::interval" } if $interval; @@ -19,6 +23,7 @@ sub nearby { %{ $c->cobrand->problems_clause }, %$params } if $c->cobrand->problems_clause; + $params->{category} = $category if $category; my $attrs = { prefetch => 'problem', diff --git a/perllib/FixMyStreet/DB/ResultSet/Problem.pm b/perllib/FixMyStreet/DB/ResultSet/Problem.pm index 7a50a3146..7885c28b3 100644 --- a/perllib/FixMyStreet/DB/ResultSet/Problem.pm +++ b/perllib/FixMyStreet/DB/ResultSet/Problem.pm @@ -131,20 +131,25 @@ sub _recent { # Problems around a location sub around_map { - my ( $rs, $min_lat, $max_lat, $min_lon, $max_lon, $interval, $limit ) = @_; + my ( $rs, $min_lat, $max_lat, $min_lon, $max_lon, $interval, $limit, $category, $states ) = @_; my $attr = { order_by => { -desc => 'created' }, }; $attr->{rows} = $limit if $limit; + unless ( $states ) { + $states = FixMyStreet::DB::Result::Problem->visible_states(); + } + my $q = { non_public => 0, - state => [ FixMyStreet::DB::Result::Problem->visible_states() ], + state => [ keys %$states ], latitude => { '>=', $min_lat, '<', $max_lat }, longitude => { '>=', $min_lon, '<', $max_lon }, }; $q->{'current_timestamp - lastupdate'} = { '<', \"'$interval'::interval" } if $interval; + $q->{category} = $category if $category; my @problems = mySociety::Locale::in_gb_locale { $rs->search( $q, $attr )->all }; return \@problems; diff --git a/perllib/FixMyStreet/Map.pm b/perllib/FixMyStreet/Map.pm index 7d490fde3..0fa23d081 100644 --- a/perllib/FixMyStreet/Map.pm +++ b/perllib/FixMyStreet/Map.pm @@ -55,7 +55,7 @@ sub display_map { } sub map_features { - my ( $c, $lat, $lon, $interval ) = @_; + my ( $c, $lat, $lon, $interval, $category, $states ) = @_; # TODO - be smarter about calculating the surrounding square # use deltas that are roughly 500m in the UK - so we get a 1 sq km search box @@ -65,12 +65,12 @@ sub map_features { $c, $lat, $lon, $lon - $lon_delta, $lat - $lat_delta, $lon + $lon_delta, $lat + $lat_delta, - $interval + $interval, $category, $states ); } sub map_features_bounds { - my ( $c, $min_lon, $min_lat, $max_lon, $max_lat, $interval ) = @_; + my ( $c, $min_lon, $min_lat, $max_lon, $max_lat, $interval, $category, $states ) = @_; my $lat = ( $max_lat + $min_lat ) / 2; my $lon = ( $max_lon + $min_lon ) / 2; @@ -78,20 +78,21 @@ sub map_features_bounds { $c, $lat, $lon, $min_lon, $min_lat, $max_lon, $max_lat, - $interval + $interval, $category, + $states ); } sub _map_features { - my ( $c, $lat, $lon, $min_lon, $min_lat, $max_lon, $max_lat, $interval ) = @_; + my ( $c, $lat, $lon, $min_lon, $min_lat, $max_lon, $max_lat, $interval, $category, $states ) = @_; # list of problems around map can be limited, but should show all pins my $around_limit = $c->cobrand->on_map_list_limit || undef; my @around_args = ( $min_lat, $max_lat, $min_lon, $max_lon, $interval ); - my $around_map = $c->cobrand->problems->around_map( @around_args, undef ); + my $around_map = $c->cobrand->problems->around_map( @around_args, undef, $category, $states ); my $around_map_list = $around_limit - ? $c->cobrand->problems->around_map( @around_args, $around_limit ) + ? $c->cobrand->problems->around_map( @around_args, $around_limit, $category, $states ) : $around_map; my $dist; @@ -105,7 +106,7 @@ sub _map_features { my $limit = 20; my @ids = map { $_->id } @$around_map_list; my $nearby = $c->model('DB::Nearby')->nearby( - $c, $dist, \@ids, $limit, $lat, $lon, $interval + $c, $dist, \@ids, $limit, $lat, $lon, $interval, $category, $states ); return ( $around_map, $around_map_list, $nearby, $dist ); @@ -116,9 +117,13 @@ sub map_pins { my $bbox = $c->req->param('bbox'); my ( $min_lon, $min_lat, $max_lon, $max_lat ) = split /,/, $bbox; + my $category = $c->req->param('filter_category'); + + $c->forward( '/reports/stash_report_filter_status' ); + my $states = $c->stash->{filter_problem_states}; my ( $around_map, $around_map_list, $nearby, $dist ) = - FixMyStreet::Map::map_features_bounds( $c, $min_lon, $min_lat, $max_lon, $max_lat, $interval ); + FixMyStreet::Map::map_features_bounds( $c, $min_lon, $min_lat, $max_lon, $max_lat, $interval, $category, $states ); # create a list of all the pins my @pins = map { diff --git a/perllib/FixMyStreet/TestMech.pm b/perllib/FixMyStreet/TestMech.pm index cd846dcd8..bd2ca4096 100644 --- a/perllib/FixMyStreet/TestMech.pm +++ b/perllib/FixMyStreet/TestMech.pm @@ -320,6 +320,7 @@ sub extract_problem_meta { my $result = scraper { process 'div#side p em', 'meta', 'TEXT'; process '.problem-header p em', 'meta', 'TEXT'; + process '.problem-header p.report_meta_info', 'meta', 'TEXT'; } ->scrape( $mech->response ); diff --git a/t/app/controller/around.t b/t/app/controller/around.t index df4f18660..03bcebf96 100644 --- a/t/app/controller/around.t +++ b/t/app/controller/around.t @@ -127,4 +127,43 @@ subtest 'check non public reports are not displayed on around page' => sub { }; +subtest 'check category and status filtering works on /ajax' => sub { + my $categories = [ 'Pothole', 'Vegetation', 'Flytipping' ]; + my $params = { + postcode => 'OX1 1ND', + latitude => 51.7435918829363, + longitude => -1.23201966270446, + }; + my $bbox = ($params->{longitude} - 0.01) . ',' . ($params->{latitude} - 0.01) + . ',' . ($params->{longitude} + 0.01) . ',' . ($params->{latitude} + 0.01); + + # Create one open and one fixed report in each category + foreach my $category ( @$categories ) { + foreach my $state ( 'confirmed', 'fixed' ) { + my %report_params = ( + %$params, + category => $category, + state => $state, + ); + $mech->create_problems_for_body( 1, 2237, 'Around page', \%report_params ); + } + } + + my $json = $mech->get_ok_json( '/ajax?bbox=' . $bbox ); + my $pins = $json->{pins}; + is scalar @$pins, 6, 'correct number of reports when no filters'; + + $json = $mech->get_ok_json( '/ajax?filter_category=Pothole&bbox=' . $bbox ); + $pins = $json->{pins}; + is scalar @$pins, 2, 'correct number of Pothole reports'; + + $json = $mech->get_ok_json( '/ajax?status=open&bbox=' . $bbox ); + $pins = $json->{pins}; + is scalar @$pins, 3, 'correct number of open reports'; + + $json = $mech->get_ok_json( '/ajax?status=fixed&filter_category=Vegetation&bbox=' . $bbox ); + $pins = $json->{pins}; + is scalar @$pins, 1, 'correct number of fixed Vegetation reports'; +}; + done_testing(); diff --git a/templates/web/base/report/_council_sent_info.html b/templates/web/base/report/_council_sent_info.html index 958562dc2..4496611e0 100644 --- a/templates/web/base/report/_council_sent_info.html +++ b/templates/web/base/report/_council_sent_info.html @@ -1,5 +1,5 @@ [% IF problem.whensent || problem.can_display_external_id %] - <small class="council_sent_info"><br> + <p class="council_sent_info"> [% problem.processed_summary_string(c) %] - </small> + </p> [% END %] diff --git a/templates/web/base/report/_main.html b/templates/web/base/report/_main.html index 00b0188af..a7aafb6d0 100644 --- a/templates/web/base/report/_main.html +++ b/templates/web/base/report/_main.html @@ -52,18 +52,19 @@ </div> [% END %] - <p><em> - [% problem.meta_line(c) | html %] - [%- IF !problem.used_map AND c.cobrand.moniker != 'emptyhomes' %]; <strong>[% loc('there is no pin shown as the user did not use the map') %]</strong>[% END %] + <p class="report_meta_info"> + [% problem.meta_line(c) | html %] + [%- IF !problem.used_map AND c.cobrand.moniker != 'emptyhomes' %]; <strong>([% loc('there is no pin shown as the user did not use the map') %])</strong>[% END %] + </p> [% IF problem.bodies_str %] [% INCLUDE 'report/_council_sent_info.html' %] [% ELSE %] - <br><small>[% loc('Not reported to council') %]</small> + <p class="council_sent_info">[% loc('Not reported to council') %]</p> [% END %] [% mlog = problem.latest_moderation_log_entry(); IF mlog %] - <br /> Moderated by [% mlog.user.from_body.name %] at [% prettify_dt(mlog.whenedited) %] + <p>Moderated by [% mlog.user.from_body.name %] at [% prettify_dt(mlog.whenedited) %]</p> [% END %] - </em></p> + [% INCLUDE 'report/_support.html' %] [% INCLUDE 'report/photo.html' object=problem %] diff --git a/templates/web/base/reports/_list-filters.html b/templates/web/base/reports/_list-filters.html new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/templates/web/base/reports/_list-filters.html diff --git a/templates/web/base/reports/_problem-list.html b/templates/web/base/reports/_problem-list.html new file mode 100644 index 000000000..45746e309 --- /dev/null +++ b/templates/web/base/reports/_problem-list.html @@ -0,0 +1,19 @@ +<section class="full-width"> + [% INCLUDE column + problems = problems.${body.id} + %] +</section> + +[% BLOCK column %] + <ul class="issue-list-a"> + [% IF problems %] + [% FOREACH problem IN problems %] + [% INCLUDE 'reports/_list-entry.html' %] + [% END %] + [% ELSE %] + <li class="empty"> + <p>[% loc('There are no reports to show.') %]</p> + </li> + [% END %] + </ul> +[% END %] diff --git a/templates/web/base/reports/body.html b/templates/web/base/reports/body.html index e1d1b359e..841e3db60 100755 --- a/templates/web/base/reports/body.html +++ b/templates/web/base/reports/body.html @@ -84,28 +84,13 @@ </p> [% END %] +[% INCLUDE "reports/_list-filters.html", use_section_wrapper = 1 %] + [% INCLUDE 'pagination.html', param = 'p' %] -<section class="full-width"> - [% INCLUDE column - problems = problems.${body.id} - %] -</section> +[% INCLUDE 'reports/_problem-list.html' %] [% INCLUDE 'pagination.html', param = 'p' %] </div> [% INCLUDE 'footer.html' %] - -[% BLOCK column %] -[% IF problems %] - -<ul class="issue-list-a"> -[% FOREACH problem IN problems %] - [% INCLUDE 'reports/_list-entry.html' %] -[% END %] -</ul> - -[% END %] -[% END %] - diff --git a/templates/web/fixmystreet/my/_problem-list.html b/templates/web/fixmystreet/my/_problem-list.html new file mode 100644 index 000000000..1a891de80 --- /dev/null +++ b/templates/web/fixmystreet/my/_problem-list.html @@ -0,0 +1,27 @@ +[% FOREACH p = problems.confirmed %] + [% IF loop.first %]<h2>[% loc('Open reports') %]</h2>[% END %] + [% INCLUDE problem %] +[% END %] + +[% FOREACH p = problems.fixed %] + [% IF loop.first %]<h2>[% loc('Fixed reports') %]</h2>[% END %] + [% INCLUDE problem %] +[% END %] + +[% FOREACH p = problems.closed %] + [% IF loop.first %]<h2>[% loc('Closed reports') %]</h2>[% END %] + [% INCLUDE problem %] +[% END %] + +[%# FOREACH p = problems.unconfirmed; + IF loop.first; + '<h2>' _ loc('Unconfirmed reports') _ '</h2>'; + END; + INCLUDE problem; +END %] + +[% BLOCK problem %] + [% "<ul class='issue-list-a full-width'>" IF loop.first %] + [% INCLUDE 'report/_item.html', problem = p, no_fixed =1 %] + [% "</ul>" IF loop.last %] +[% END %] diff --git a/templates/web/fixmystreet/my/my.html b/templates/web/fixmystreet/my/my.html index 3944c2b1c..9c857aab9 100644 --- a/templates/web/fixmystreet/my/my.html +++ b/templates/web/fixmystreet/my/my.html @@ -27,32 +27,14 @@ [% INCLUDE '_hart_hants_note.html' %] [% END %] +[% INCLUDE "reports/_list-filters.html", use_section_wrapper = 1 %] + [% INCLUDE 'pagination.html', pager = problems_pager, param = 'p' %] -[% FOREACH p = problems.confirmed %] - [% IF loop.first %]<h2>[% loc('Open reports') %]</h2>[% END %] - [% INCLUDE problem %] -[% END %] - -[% FOREACH p = problems.fixed %] - [% IF loop.first %]<h2>[% loc('Fixed reports') %]</h2>[% END %] - [% INCLUDE problem %] -[% END %] - -[% FOREACH p = problems.closed %] - [% IF loop.first %]<h2>[% loc('Closed reports') %]</h2>[% END %] - [% INCLUDE problem %] -[% END %] - -[%# FOREACH p = problems.unconfirmed; - IF loop.first; - '<h2>' _ loc('Unconfirmed reports') _ '</h2>'; - END; - INCLUDE problem; -END %] +[% INCLUDE 'my/_problem-list.html' %] [% FOREACH u IN updates %] [% IF loop.first %] diff --git a/web/cobrands/sass/_base.scss b/web/cobrands/sass/_base.scss index a26f67ee8..a9ffe7295 100644 --- a/web/cobrands/sass/_base.scss +++ b/web/cobrands/sass/_base.scss @@ -976,6 +976,19 @@ a:hover.button-left { margin-bottom: 0.5em; } +.report_meta_info, +.council_sent_info { + font-size: 0.9em; +} + +.council_sent_info { + color: #666; + + p + & { + margin-top: -0.6em; // partly counteract margin-bottom on previous paragraph + } +} + // map stuff #map_box{ @extend .full-width; diff --git a/web/cobrands/sass/_report_list.scss b/web/cobrands/sass/_report_list.scss new file mode 100644 index 000000000..b4e150d03 --- /dev/null +++ b/web/cobrands/sass/_report_list.scss @@ -0,0 +1,155 @@ +// You should @import this file in a cobrand's layout.scss if it's using +// the new-style combined report list with category/status filters. + +.report-list-filters { + padding: 1em 1em 0 1em; + margin-bottom: 0.5em; + color: #666; + font-size: 0.85em; + + label, select { + display: inline-block; + width: auto; + } + + label { + font-weight: normal; + margin: 0; + + & + label { + margin-left: 0.2em; + } + } + + select { + color: inherit; + font-family: inherit; + font-size: 1em; + + border: 1px solid #ced7c4; + box-shadow: 0 1px 0 #fff; + height: 2em; + margin-left: 0.2em; + max-width: 13em; + } +} + +.report-list, .issue-list-a { + margin-left: 0; + + li { + list-style: none; + position: relative; + margin: 0; + background: none; + + a { + display: block; + padding: 1em; + padding-left: 4em; + background: transparent url(/i/pin-yellow-small.png) no-repeat 1em center; + + &:hover, &:focus { + background-color: #fff; + background-repeat: no-repeat; + background-position: 1em center; + background: transparent url(/i/pin-yellow-small.png) no-repeat 1em center; + text-decoration: none; + } + } + + &.yellow a { + background-image: url(/i/pin-yellow-small.png); + } + &.green a { + background-image: url(/i/pin-green-small.png); + } + &.red a { + background-image: url(/i/pin-red-small.png); + } + &.grey a { + background-image: url(/i/pin-grey-small.png); + } + + &.empty p { + display: block; + padding: 1em; + font-size: 1em; + text-align: center; + } + + &:after { + content: ""; + display: block; + height: 1px; + position: absolute; + left: 4em; + right: 0; + bottom: 0; + background-color: #e5e5e5; + } + + &.empty:after { + left: 0; + } + } + + h3, p { + margin: 0; + } + + h3 { + color: $primary; + margin-bottom: 0.2em; + } + + p { + font-size: 0.8em; + color: #777; + } +} + +.big-green-banner { + display: none; // hide the empty banner by default + + &.mobile-map-banner { + display: block; // show it again once the mobile javascript adds this class + } +} + +.click-the-map { + color: #000; + margin: -10px -1em 0 -1em; // overlap padding on parents + padding: 18px; + border-bottom: 1px solid #e5e5e5; + background: #fff url('/i/click-map-chevron-big.gif') 90% 12px no-repeat; + + h2 { + font-family: inherit; + margin: 0 0 5px 0; + } + + p { + margin: 0; + font-size: 18px; + line-height: 20px; + color: $primary; + padding-right: 20px; + background: transparent url('/i/click-map-chevron-small.gif') 100% center no-repeat; + display: inline-block; + } + + img { + // the little chevron icon + vertical-align: -1px; + margin-left: 0.2em; + } +} + +body.frontpage { + .issue-list-a, .list-a { + li .text { + padding-left: 3em; + } + } +} diff --git a/web/i/click-map-chevron-big.gif b/web/i/click-map-chevron-big.gif Binary files differnew file mode 100644 index 000000000..3610c50c8 --- /dev/null +++ b/web/i/click-map-chevron-big.gif diff --git a/web/i/click-map-chevron-small.gif b/web/i/click-map-chevron-small.gif Binary files differnew file mode 100644 index 000000000..4ffd2a95f --- /dev/null +++ b/web/i/click-map-chevron-small.gif diff --git a/web/i/pin-green-big.png b/web/i/pin-green-big.png Binary files differindex 16d73230a..69815463e 100644 --- a/web/i/pin-green-big.png +++ b/web/i/pin-green-big.png diff --git a/web/i/pin-green-mini.png b/web/i/pin-green-mini.png Binary files differnew file mode 100644 index 000000000..32d57a807 --- /dev/null +++ b/web/i/pin-green-mini.png diff --git a/web/i/pin-green-small.png b/web/i/pin-green-small.png Binary files differnew file mode 100644 index 000000000..4813ceb58 --- /dev/null +++ b/web/i/pin-green-small.png diff --git a/web/i/pin-green.png b/web/i/pin-green.png Binary files differindex 47a0a6cc1..597bfc6b7 100644 --- a/web/i/pin-green.png +++ b/web/i/pin-green.png diff --git a/web/i/pin-grey-mini.png b/web/i/pin-grey-mini.png Binary files differnew file mode 100644 index 000000000..152b0a60f --- /dev/null +++ b/web/i/pin-grey-mini.png diff --git a/web/i/pin-grey-small.png b/web/i/pin-grey-small.png Binary files differnew file mode 100644 index 000000000..14b1f9b03 --- /dev/null +++ b/web/i/pin-grey-small.png diff --git a/web/i/pin-red-big.png b/web/i/pin-red-big.png Binary files differindex 2d970b9e2..fb51b6c78 100644 --- a/web/i/pin-red-big.png +++ b/web/i/pin-red-big.png diff --git a/web/i/pin-red-mini.png b/web/i/pin-red-mini.png Binary files differnew file mode 100644 index 000000000..d91aac979 --- /dev/null +++ b/web/i/pin-red-mini.png diff --git a/web/i/pin-red-small.png b/web/i/pin-red-small.png Binary files differnew file mode 100644 index 000000000..a0e8fb0d2 --- /dev/null +++ b/web/i/pin-red-small.png diff --git a/web/i/pin-red.png b/web/i/pin-red.png Binary files differindex 298f4d3f6..882446a22 100644 --- a/web/i/pin-red.png +++ b/web/i/pin-red.png diff --git a/web/i/pin-shadow-small.png b/web/i/pin-shadow-small.png Binary files differnew file mode 100644 index 000000000..911e3a66c --- /dev/null +++ b/web/i/pin-shadow-small.png diff --git a/web/i/pin-yellow-big.png b/web/i/pin-yellow-big.png Binary files differindex d1642d644..cfae00afc 100644 --- a/web/i/pin-yellow-big.png +++ b/web/i/pin-yellow-big.png diff --git a/web/i/pin-yellow-mini.png b/web/i/pin-yellow-mini.png Binary files differnew file mode 100644 index 000000000..8181bce96 --- /dev/null +++ b/web/i/pin-yellow-mini.png diff --git a/web/i/pin-yellow-small.png b/web/i/pin-yellow-small.png Binary files differnew file mode 100644 index 000000000..53cfb654a --- /dev/null +++ b/web/i/pin-yellow-small.png diff --git a/web/i/pin-yellow.png b/web/i/pin-yellow.png Binary files differindex 196cb0f5f..67ccc546a 100644 --- a/web/i/pin-yellow.png +++ b/web/i/pin-yellow.png diff --git a/web/js/map-OpenLayers.js b/web/js/map-OpenLayers.js index 3088cc764..a0ab4f34c 100644 --- a/web/js/map-OpenLayers.js +++ b/web/js/map-OpenLayers.js @@ -29,6 +29,13 @@ function fixmystreet_update_pin(lonlat) { if ( lb.length === 0 ) { lb = $('#form_name').prev(); } lb.before(data.extra_name_info); } + // If the category filter appears on the map and the user has selected + // something from it, then pre-fill the category field in the report, + // if it's a value already present in the drop-down. + var category = $("#filter_categories").val(); + if (category !== undefined && $("#form_category option[value="+category+"]").length) { + $("#form_category").val(category); + } }); if (!$('#side-form-error').is(':visible')) { @@ -69,6 +76,7 @@ function fixmystreet_zoomToBounds(bounds) { function fms_markers_list(pins, transform) { var markers = []; + var size = fms_marker_size_for_zoom(fixmystreet.map.getZoom() + fixmystreet.zoomOffset); for (var i=0; i<pins.length; i++) { var pin = pins[i]; var loc = new OpenLayers.Geometry.Point(pin[1], pin[0]); @@ -81,7 +89,7 @@ function fms_markers_list(pins, transform) { } var marker = new OpenLayers.Feature.Vector(loc, { colour: pin[2], - size: pin[5] || 'normal', + size: pin[5] || size, id: pin[3], title: pin[4] || '' }); @@ -90,6 +98,29 @@ function fms_markers_list(pins, transform) { return markers; } +function fms_marker_size_for_zoom(zoom) { + if (zoom >= 15) { + return 'normal'; + } else if (zoom >= 13) { + return 'small'; + } else { + return 'mini'; + } +} + +function fms_markers_resize() { + var size = fms_marker_size_for_zoom(fixmystreet.map.getZoom() + fixmystreet.zoomOffset); + for (var i = 0; i < fixmystreet.markers.features.length; i++) { + fixmystreet.markers.features[i].attributes.size = size; + } + fixmystreet.markers.redraw(); +} + +function fms_categories_or_status_changed() { + // If the category or status has changed we need to re-fetch map markers + fixmystreet.markers.refresh({force: true}); +} + function fixmystreet_onload() { if ( fixmystreet.area.length ) { for (var i=0; i<fixmystreet.area.length; i++) { @@ -131,7 +162,8 @@ function fixmystreet_onload() { backgroundWidth: 60, backgroundHeight: 30, backgroundXOffset: -7, - backgroundYOffset: -30 + backgroundYOffset: -30, + popupYOffset: -40 }, 'big': { externalGraphic: fixmystreet.pin_prefix + "pin-${colour}-big.png", @@ -144,6 +176,27 @@ function fixmystreet_onload() { backgroundHeight: 40, backgroundXOffset: -10, backgroundYOffset: -35 + }, + 'small': { + externalGraphic: fixmystreet.pin_prefix + "pin-${colour}-small.png", + graphicWidth: 24, + graphicHeight: 32, + graphicXOffset: -12, + graphicYOffset: -32, + backgroundGraphic: fixmystreet.pin_prefix + "pin-shadow-small.png", + backgroundWidth: 30, + backgroundHeight: 15, + backgroundXOffset: -4, + backgroundYOffset: -15, + popupYOffset: -20 + }, + 'mini': { + externalGraphic: fixmystreet.pin_prefix + "pin-${colour}-mini.png", + graphicWidth: 16, + graphicHeight: 20, + graphicXOffset: -8, + graphicYOffset: -20, + popupYOffset: -10 } }); var pin_layer_options = { @@ -155,7 +208,7 @@ function fixmystreet_onload() { if (fixmystreet.page == 'around') { fixmystreet.bbox_strategy = fixmystreet.bbox_strategy || new OpenLayers.Strategy.BBOX({ ratio: 1 }); pin_layer_options.strategies = [ fixmystreet.bbox_strategy ]; - pin_layer_options.protocol = new OpenLayers.Protocol.HTTP({ + pin_layer_options.protocol = new OpenLayers.Protocol.FixMyStreet({ url: '/ajax', params: fixmystreet.all_pins ? { all_pins: 1 } : { }, format: new OpenLayers.Format.FixMyStreet() @@ -186,17 +239,29 @@ function fixmystreet_onload() { fixmystreet.markers.events.register( 'featureselected', fixmystreet.markers, function(evt) { var feature = evt.feature; selectedFeature = feature; + var popupYOffset = feature.layer.styleMap.createSymbolizer(feature).popupYOffset || -40; var popup = new OpenLayers.Popup.FramedCloud("popup", feature.geometry.getBounds().getCenterLonLat(), null, feature.attributes.title + "<br><a href=/report/" + feature.attributes.id + ">" + translation_strings.more_details + "</a>", - { size: new OpenLayers.Size(0,0), offset: new OpenLayers.Pixel(0,-40) }, + { size: new OpenLayers.Size(0, 0), offset: new OpenLayers.Pixel(0, popupYOffset) }, true, onPopupClose); feature.popup = popup; fixmystreet.map.addPopup(popup); }); fixmystreet.map.addControl( fixmystreet.select_feature ); fixmystreet.select_feature.activate(); + fixmystreet.map.events.register( 'zoomend', null, fms_markers_resize ); + + // If the category filter dropdown exists on the page set up the + // event handlers to populate it and react to it changing + if ($("select#filter_categories").length) { + $("body").on("change", "#filter_categories", fms_categories_or_status_changed); + } + // Do the same for the status dropdown + if ($("select#statuses").length) { + $("body").on("change", "#statuses", fms_categories_or_status_changed); + } } else if (fixmystreet.page == 'new') { fixmystreet_activate_drag(); } @@ -354,6 +419,16 @@ $(function(){ fixmystreet.page = 'around'; }); + // Hide the pin filter submit button. Not needed because we'll use JS + // to refresh the map when the filter inputs are changed. + $(".report-list-filters [type=submit]").hide(); + + if (fixmystreet.page == "my" || fixmystreet.page == "reports") { + $(".report-list-filters select").change(function() { + $(this).closest("form").submit(); + }); + } + // Vector layers must be added onload as IE sucks if ($.browser.msie) { $(window).load(fixmystreet_onload); @@ -447,6 +522,30 @@ OpenLayers.Control.PermalinkFMSz = OpenLayers.Class(OpenLayers.Control.Permalink } }); +/* Pan data request handler */ +// This class is used to get a JSON object from /ajax that contains +// pins for the map and HTML for the sidebar. It does a fetch whenever the map +// is dragged (modulo a buffer extending outside the viewport). +// This subclass is required so we can pass the 'filter_category' and 'status' query +// params to /ajax if the user has filtered the map. +OpenLayers.Protocol.FixMyStreet = OpenLayers.Class(OpenLayers.Protocol.HTTP, { + read: function(options) { + // Pass the values of the category and status fields as query params + var filter_category = $("#filter_categories").val(); + if (filter_category !== undefined) { + options.params = options.params || {}; + options.params.filter_category = filter_category; + } + var status = $("#statuses").val(); + if (status !== undefined) { + options.params = options.params || {}; + options.params.status = status; + } + return OpenLayers.Protocol.HTTP.prototype.read.apply(this, [options]); + }, + CLASS_NAME: "OpenLayers.Protocol.FixMyStreet" +}); + /* Pan data handler */ OpenLayers.Format.FixMyStreet = OpenLayers.Class(OpenLayers.Format.JSON, { read: function(json, filter) { @@ -462,8 +561,7 @@ OpenLayers.Format.FixMyStreet = OpenLayers.Class(OpenLayers.Format.JSON, { if (typeof(obj.current_near) != 'undefined' && (current_near = document.getElementById('current_near'))) { current_near.innerHTML = obj.current_near; } - var markers = fms_markers_list( obj.pins, false ); - return markers; + return fms_markers_list( obj.pins, false ); }, CLASS_NAME: "OpenLayers.Format.FixMyStreet" }); |