aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--perllib/FixMyStreet/App/Controller/Around.pm46
-rw-r--r--perllib/FixMyStreet/App/Controller/My.pm20
-rw-r--r--perllib/FixMyStreet/App/Controller/Reports.pm38
-rw-r--r--perllib/FixMyStreet/Cobrand/Default.pm18
-rw-r--r--perllib/FixMyStreet/DB/Result/Problem.pm8
-rw-r--r--perllib/FixMyStreet/DB/ResultSet/Nearby.pm9
-rw-r--r--perllib/FixMyStreet/DB/ResultSet/Problem.pm9
-rw-r--r--perllib/FixMyStreet/Map.pm23
-rw-r--r--perllib/FixMyStreet/TestMech.pm1
-rw-r--r--t/app/controller/around.t39
-rw-r--r--templates/web/base/report/_council_sent_info.html4
-rw-r--r--templates/web/base/report/_main.html13
-rw-r--r--templates/web/base/reports/_list-filters.html0
-rw-r--r--templates/web/base/reports/_problem-list.html19
-rwxr-xr-xtemplates/web/base/reports/body.html21
-rw-r--r--templates/web/fixmystreet/my/_problem-list.html27
-rw-r--r--templates/web/fixmystreet/my/my.html24
-rw-r--r--web/cobrands/sass/_base.scss13
-rw-r--r--web/cobrands/sass/_report_list.scss155
-rw-r--r--web/i/click-map-chevron-big.gifbin0 -> 576 bytes
-rw-r--r--web/i/click-map-chevron-small.gifbin0 -> 3261 bytes
-rw-r--r--web/i/pin-green-big.pngbin3270 -> 4305 bytes
-rw-r--r--web/i/pin-green-mini.pngbin0 -> 577 bytes
-rw-r--r--web/i/pin-green-small.pngbin0 -> 1141 bytes
-rw-r--r--web/i/pin-green.pngbin1316 -> 2191 bytes
-rw-r--r--web/i/pin-grey-mini.pngbin0 -> 611 bytes
-rw-r--r--web/i/pin-grey-small.pngbin0 -> 875 bytes
-rw-r--r--web/i/pin-red-big.pngbin3401 -> 5002 bytes
-rw-r--r--web/i/pin-red-mini.pngbin0 -> 633 bytes
-rw-r--r--web/i/pin-red-small.pngbin0 -> 1148 bytes
-rw-r--r--web/i/pin-red.pngbin1353 -> 2494 bytes
-rw-r--r--web/i/pin-shadow-small.pngbin0 -> 521 bytes
-rw-r--r--web/i/pin-yellow-big.pngbin1242 -> 2867 bytes
-rw-r--r--web/i/pin-yellow-mini.pngbin0 -> 509 bytes
-rw-r--r--web/i/pin-yellow-small.pngbin0 -> 877 bytes
-rw-r--r--web/i/pin-yellow.pngbin828 -> 1522 bytes
-rw-r--r--web/js/map-OpenLayers.js110
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:&nbsp;%s'), $problem->external_id) . '.</strong>';
} else {
- $external_ref_clause = sprintf(_('%s ref:&nbsp;%s'), $problem->external_body, $problem->external_id);
+ $external_ref_clause = '<strong>' . sprintf(_('%s ref:&nbsp;%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
new file mode 100644
index 000000000..3610c50c8
--- /dev/null
+++ b/web/i/click-map-chevron-big.gif
Binary files differ
diff --git a/web/i/click-map-chevron-small.gif b/web/i/click-map-chevron-small.gif
new file mode 100644
index 000000000..4ffd2a95f
--- /dev/null
+++ b/web/i/click-map-chevron-small.gif
Binary files differ
diff --git a/web/i/pin-green-big.png b/web/i/pin-green-big.png
index 16d73230a..69815463e 100644
--- a/web/i/pin-green-big.png
+++ b/web/i/pin-green-big.png
Binary files differ
diff --git a/web/i/pin-green-mini.png b/web/i/pin-green-mini.png
new file mode 100644
index 000000000..32d57a807
--- /dev/null
+++ b/web/i/pin-green-mini.png
Binary files differ
diff --git a/web/i/pin-green-small.png b/web/i/pin-green-small.png
new file mode 100644
index 000000000..4813ceb58
--- /dev/null
+++ b/web/i/pin-green-small.png
Binary files differ
diff --git a/web/i/pin-green.png b/web/i/pin-green.png
index 47a0a6cc1..597bfc6b7 100644
--- a/web/i/pin-green.png
+++ b/web/i/pin-green.png
Binary files differ
diff --git a/web/i/pin-grey-mini.png b/web/i/pin-grey-mini.png
new file mode 100644
index 000000000..152b0a60f
--- /dev/null
+++ b/web/i/pin-grey-mini.png
Binary files differ
diff --git a/web/i/pin-grey-small.png b/web/i/pin-grey-small.png
new file mode 100644
index 000000000..14b1f9b03
--- /dev/null
+++ b/web/i/pin-grey-small.png
Binary files differ
diff --git a/web/i/pin-red-big.png b/web/i/pin-red-big.png
index 2d970b9e2..fb51b6c78 100644
--- a/web/i/pin-red-big.png
+++ b/web/i/pin-red-big.png
Binary files differ
diff --git a/web/i/pin-red-mini.png b/web/i/pin-red-mini.png
new file mode 100644
index 000000000..d91aac979
--- /dev/null
+++ b/web/i/pin-red-mini.png
Binary files differ
diff --git a/web/i/pin-red-small.png b/web/i/pin-red-small.png
new file mode 100644
index 000000000..a0e8fb0d2
--- /dev/null
+++ b/web/i/pin-red-small.png
Binary files differ
diff --git a/web/i/pin-red.png b/web/i/pin-red.png
index 298f4d3f6..882446a22 100644
--- a/web/i/pin-red.png
+++ b/web/i/pin-red.png
Binary files differ
diff --git a/web/i/pin-shadow-small.png b/web/i/pin-shadow-small.png
new file mode 100644
index 000000000..911e3a66c
--- /dev/null
+++ b/web/i/pin-shadow-small.png
Binary files differ
diff --git a/web/i/pin-yellow-big.png b/web/i/pin-yellow-big.png
index d1642d644..cfae00afc 100644
--- a/web/i/pin-yellow-big.png
+++ b/web/i/pin-yellow-big.png
Binary files differ
diff --git a/web/i/pin-yellow-mini.png b/web/i/pin-yellow-mini.png
new file mode 100644
index 000000000..8181bce96
--- /dev/null
+++ b/web/i/pin-yellow-mini.png
Binary files differ
diff --git a/web/i/pin-yellow-small.png b/web/i/pin-yellow-small.png
new file mode 100644
index 000000000..53cfb654a
--- /dev/null
+++ b/web/i/pin-yellow-small.png
Binary files differ
diff --git a/web/i/pin-yellow.png b/web/i/pin-yellow.png
index 196cb0f5f..67ccc546a 100644
--- a/web/i/pin-yellow.png
+++ b/web/i/pin-yellow.png
Binary files differ
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"
});