diff options
20 files changed, 788 insertions, 94 deletions
diff --git a/perllib/FixMyStreet/App/Controller/Admin.pm b/perllib/FixMyStreet/App/Controller/Admin.pm index 5373220a7..c2c4e7588 100644 --- a/perllib/FixMyStreet/App/Controller/Admin.pm +++ b/perllib/FixMyStreet/App/Controller/Admin.pm @@ -678,6 +678,9 @@ sub categories_for_point : Private { shift @{$c->stash->{category_options}} if @{$c->stash->{category_options}}; $c->stash->{categories_hash} = { map { $_->category => 1 } @{$c->stash->{category_options}} }; + + $c->forward('/admin/triage/setup_categories'); + } sub alerts_for_report : Private { diff --git a/perllib/FixMyStreet/App/Controller/Admin/Triage.pm b/perllib/FixMyStreet/App/Controller/Admin/Triage.pm new file mode 100644 index 000000000..42028a6f0 --- /dev/null +++ b/perllib/FixMyStreet/App/Controller/Admin/Triage.pm @@ -0,0 +1,149 @@ +package FixMyStreet::App::Controller::Admin::Triage; +use Moose; +use namespace::autoclean; + +BEGIN { extends 'Catalyst::Controller'; } + +=head1 NAME + +FixMyStreet::App::Controller::Admin::Triage - Catalyst Controller + +=head1 DESCRIPTION + +Admin pages for triaging reports. + +This allows reports to be triaged before being sent to the council. It works +by having a set of categories with a send_method of Triage which sets the report +state to 'for_triage'. Any reports with the state are then show on '/admin/triage' +which is available to users with the 'triage' permission. + +Clicking on reports on this list will then allow a user to change the category of +the report to one that has an alternative send method, which will trigger the report +to be resent. + +In order for this to work additional work needs to be done to the cobrand to only +display triageable categories to the user. + +=head1 METHODS + +=cut + +sub auto : Private { + my ( $self, $c ) = @_; + + unless ( $c->user->has_body_permission_to('triage') ) { + $c->detach('/page_error_403_access_denied', []); + } +} + +sub index : Path : Args(0) { + my ( $self, $c ) = @_; + + # default sort to oldest + unless ( $c->get_param('sort') ) { + $c->set_param('sort', 'created-asc'); + } + $c->stash->{body} = $c->forward('/reports/body_find', [ $c->cobrand->council_area ]); + $c->forward( 'stash_report_filter_status' ); + $c->forward( '/reports/load_and_group_problems' ); + $c->stash->{page} = 'reports'; # So the map knows to make clickable pins + + if ($c->get_param('ajax')) { + my $ajax_template = $c->stash->{ajax_template} || 'reports/_problem-list.html'; + $c->detach('/reports/ajax', [ $ajax_template ]); + } + + my @categories = $c->stash->{body}->contacts->not_deleted->search( undef, { + columns => [ 'id', 'category', 'extra' ], + distinct => 1, + order_by => [ 'category' ], + } )->all; + $c->stash->{filter_categories} = \@categories; + $c->stash->{filter_category} = { map { $_ => 1 } $c->get_param_list('filter_category', 1) }; + my $pins = $c->stash->{pins} || []; + + my %map_params = ( + latitude => @$pins ? $pins->[0]{latitude} : 0, + longitude => @$pins ? $pins->[0]{longitude} : 0, + area => [ $c->stash->{wards} ? map { $_->{id} } @{$c->stash->{wards}} : keys %{$c->stash->{body}->areas} ], + any_zoom => 1, + ); + FixMyStreet::Map::display_map( + $c, %map_params, pins => $pins, + ); +} + +sub stash_report_filter_status : Private { + my ( $self, $c ) = @_; + $c->stash->{filter_problem_states} = { 'for triage' => 1 }; + return 1; +} + +sub setup_categories : Private { + my ( $self, $c ) = @_; + + if ( $c->stash->{problem}->state eq 'for triage' ) { + $c->stash->{holding_options} = [ grep { $_->send_method && $_->send_method eq 'Triage' } @{$c->stash->{category_options}} ]; + $c->stash->{holding_categories} = { map { $_->category => 1 } @{$c->stash->{holding_options}} }; + $c->stash->{end_options} = [ grep { !$_->send_method || $_->send_method ne 'Triage' } @{$c->stash->{category_options}} ]; + $c->stash->{end_categories} = { map { $_->category => 1 } @{$c->stash->{end_options}} }; + delete $c->stash->{categories_hash}; + my %category_groups = (); + for my $category (@{$c->stash->{end_options}}) { + my $group = $category->{group} // $category->get_extra_metadata('group') // ['']; + # this could be an array ref or a string + my @groups = ref $group eq 'ARRAY' ? @$group : ($group); + push( @{$category_groups{$_}}, $category ) for @groups; + } + my @category_groups = (); + for my $group ( grep { $_ ne _('Other') } sort keys %category_groups ) { + push @category_groups, { name => $group, categories => $category_groups{$group} }; + } + $c->stash->{end_groups} = \@category_groups; + } + + return 1; +} + +sub update : Private { + my ($self, $c) = @_; + + my $problem = $c->stash->{problem}; + + my $current_category = $problem->category; + my $new_category = $c->get_param('category'); + + my $changed = $c->forward('/admin/report_edit_category', [ $problem, 1 ] ); + + if ( $changed ) { + $c->stash->{problem}->update( { state => 'confirmed' } ); + $c->forward( '/admin/log_edit', [ $problem->id, 'problem', 'triage' ] ); + + my $name = $c->user->moderating_user_name; + my $extra = { is_superuser => 1 }; + if ($c->user->from_body) { + delete $extra->{is_superuser}; + $extra->{is_body_user} = $c->user->from_body->id; + } + + $extra->{triage_report} = 1; + $extra->{holding_category} = $current_category; + $extra->{new_category} = $new_category; + + my $timestamp = \'current_timestamp'; + $problem->add_to_comments( { + text => "Report triaged from $current_category to $new_category", + created => $timestamp, + confirmed => $timestamp, + user_id => $c->user->id, + name => $name, + mark_fixed => 0, + anonymous => 0, + state => 'confirmed', + problem_state => $problem->state, + extra => $extra + } ); + } +} + +1; diff --git a/perllib/FixMyStreet/App/Controller/Report.pm b/perllib/FixMyStreet/App/Controller/Report.pm index 9b90da161..eb6050fde 100644 --- a/perllib/FixMyStreet/App/Controller/Report.pm +++ b/perllib/FixMyStreet/App/Controller/Report.pm @@ -86,7 +86,7 @@ sub display :PathPart('') :Chained('id') :Args(0) { $c->forward( 'format_problem_for_display' ); my $permissions = $c->stash->{_permissions} ||= $c->forward( 'check_has_permission_to', - [ qw/report_inspect report_edit_category report_edit_priority report_mark_private/ ] ); + [ qw/report_inspect report_edit_category report_edit_priority report_mark_private triage/ ] ); if (any { $_ } values %$permissions) { $c->stash->{template} = 'report/inspect.html'; $c->forward('inspect'); @@ -396,7 +396,14 @@ sub inspect : Private { $c->stash->{max_detailed_info_length} = $c->cobrand->max_detailed_info_length; - if ( $c->get_param('save') ) { + if ( $c->get_param('triage') ) { + $c->forward('/auth/check_csrf_token'); + $c->forward('/admin/triage/update'); + my $redirect_uri = $c->uri_for( '/admin/triage' ); + $c->log->debug( "Redirecting to: " . $redirect_uri ); + $c->res->redirect( $redirect_uri ); + } + elsif ( $c->get_param('save') ) { $c->forward('/auth/check_csrf_token'); my $valid = 1; diff --git a/perllib/FixMyStreet/Cobrand/IsleOfWight.pm b/perllib/FixMyStreet/Cobrand/IsleOfWight.pm index 9e48b4d77..f2cd1a687 100644 --- a/perllib/FixMyStreet/Cobrand/IsleOfWight.pm +++ b/perllib/FixMyStreet/Cobrand/IsleOfWight.pm @@ -47,15 +47,6 @@ sub updates_disallowed { sub get_geocoder { 'OSM' } -sub open311_get_update_munging { - my ($self, $comment) = @_; - - # If we've received an update via Open311 that's closed - # or fixed the report, also close it to updates. - $comment->problem->set_extra_metadata(closed_updates => 1) - if !$comment->problem->is_open; -} - sub open311_pre_send { my ($self, $row, $open311) = @_; @@ -89,7 +80,51 @@ sub open311_munge_update_params { $params->{description} = "[The customer indicated that this issue had been fixed]\n\n" . $params->{description}; } + if ( $comment->get_extra_metadata('triage_report') ) { + $params->{description} = "Triaged by " . $comment->user->name . ' (' . $comment->user->email . "). " . $params->{description}; + } + $params->{description} = "FMS-Update: " . $params->{description}; } +sub munge_category_list { + my ($self, $options, $contacts, $extras) = @_; + + my $user = $self->{c}->user; + my %bodies = map { $_->body->name => $_->body } @$contacts; + my $b = $bodies{'Isle of Wight Council'}; + + if ( $user && ( $user->is_superuser || $user->belongs_to_body( $b->id ) ) ) { + return; + } + + @$contacts = grep { $_->send_method eq 'Triage' } @$contacts; + my $seen = { map { $_->category => 1 } @$contacts }; + @$options = grep { my $c = ($_->{category} || $_->category); $c =~ 'Pick a category' || $seen->{ $c } } @$options; +} + +sub open311_get_update_munging { + my ($self, $comment) = @_; + + # If we've received an update via Open311 that's closed + # or fixed the report, also close it to updates. + $comment->problem->set_extra_metadata(closed_updates => 1) + if !$comment->problem->is_open; +} + +sub admin_pages { + my $self = shift; + my $pages = $self->next::method(); + $pages->{triage} = [ undef, undef ]; + return $pages; +} + +sub available_permissions { + my $self = shift; + + my $perms = $self->next::method(); + $perms->{Problems}->{triage} = "Triage reports"; + + return $perms; +} 1; diff --git a/t/app/controller/admin/triage.t b/t/app/controller/admin/triage.t new file mode 100644 index 000000000..81eb28a5d --- /dev/null +++ b/t/app/controller/admin/triage.t @@ -0,0 +1,112 @@ +use FixMyStreet::TestMech; + +my $mech = FixMyStreet::TestMech->new; + +my $user = $mech->create_user_ok('test@example.com', name => 'Test User'); +my $user2 = $mech->create_user_ok('test2@example.com', name => 'Test User 2'); +my $superuser = $mech->create_user_ok( + 'superuser@example.com', + name => 'Super User', + is_superuser => 1 +); + +my $iow = $mech->create_body_ok(2636, 'Isle of Wight Council', { can_be_devolved => 1 } ); +my $iow_contact = $mech->create_contact_ok( + body_id => $iow->id, + category => 'Potholes', + email => 'potholes@example.com', + send_method => 'Triage' +); +$mech->create_contact_ok( + body_id => $iow->id, + category => 'Traffic lights', + email => 'lights@example.com' +); + +my $dt = DateTime->now(); + +my ($report) = $mech->create_problems_for_body( + 1, + $iow->id, + 'TITLE', + { + areas => 2636, + category => 'Potholes', + whensent => $dt, + latitude => 50.71086, + longitude => -1.29573, + send_method_used => 'Triage', + } +); + +warn $report->bodies_str; + +FixMyStreet::override_config { + STAGING_FLAGS => { send_reports => 1, skip_checks => 0 }, + ALLOWED_COBRANDS => [ 'isleofwight' ], + MAPIT_URL => 'http://mapit.uk/', +}, sub { + subtest "user can access triage page with triage permission" => sub { + $user->update({ from_body => $iow }); + $mech->log_out_ok; + $mech->get_ok('/admin/triage'); + + $mech->log_in_ok($user->email); + $mech->get('/admin/triage'); + is $mech->res->code, 403, 'permission denied'; + + $user->user_body_permissions->create( { body => $iow, permission_type => 'triage' } ); + $mech->get_ok('/admin/triage'); + }; + + subtest "reports marked for triage show triage interface" => sub { + $mech->log_out_ok; + $mech->log_in_ok( $user->email ); + + $mech->get_ok('/report/' . $report->id); + $mech->content_lacks('CONFIRM Subject'); + + $report->update( { state => 'for triage' } ); + + $mech->get_ok('/report/' . $report->id); + $mech->content_contains('CONFIRM Subject'); + }; + + subtest "changing report category marks report as confirmed" => sub { + my $report_url = '/report/' . $report->id; + $mech->get_ok($report_url); + + $mech->content_contains('Traffic lights'); + + $mech->submit_form_ok( { + with_fields => { + category => 'Traffic lights', + include_update => 0, + } + }, + 'triage form submitted' + ); + + $mech->content_contains('Potholes'); + + $report->discard_changes; + is $report->state, 'confirmed', 'report marked as confirmed'; + ok !$report->whensent, 'report marked to resend'; + + my @comments = $report->comments; + my $comment = $comments[0]; + my $extra = $comment->get_extra_metadata(); + is $extra->{triage_report}, 1, 'comment indicates it is for triage in extra'; + is $extra->{holding_category}, 'Potholes', 'comment extra has previous category'; + is $extra->{new_category}, 'Traffic lights', 'comment extra has new category'; + + $mech->get_ok($report_url); + $mech->content_contains('Report triaged from Potholes to Traffic lights'); + + $mech->log_out_ok; + $mech->get_ok($report_url); + $mech->content_lacks('Report triaged from Potholes to Traffic lights'); + }; +}; + +done_testing(); diff --git a/t/cobrand/isleofwight.t b/t/cobrand/isleofwight.t index 6c6450028..414b683cd 100644 --- a/t/cobrand/isleofwight.t +++ b/t/cobrand/isleofwight.t @@ -212,7 +212,6 @@ subtest "fixing passes along the correct message" => sub { }; }; - subtest 'Check special Open311 request handling', sub { my ($p) = $mech->create_problems_for_body(1, $isleofwight->id, 'Title', { category => 'Potholes', latitude => 50.7108, longitude => -1.29573 }); $p->set_extra_fields({ name => 'urgent', value => 'no'}); @@ -237,6 +236,44 @@ subtest 'Check special Open311 request handling', sub { is $c->param('attribute[urgent]'), undef, 'no urgent param sent'; }; +subtest "triaging a report includes user in message" => sub { + FixMyStreet::override_config { + MAPIT_URL => 'http://mapit.uk/', + ALLOWED_COBRANDS => 'isleofwight', + }, sub { + my $test_res = HTTP::Response->new(); + $test_res->code(200); + $test_res->message('OK'); + $test_res->content('<?xml version="1.0" encoding="utf-8"?><service_request_updates><request_update><update_id>248</update_id></request_update></service_request_updates>'); + + my $o = Open311->new( + fixmystreet_body => $isleofwight, + test_mode => 1, + test_get_returns => { 'servicerequestupdates.xml' => $test_res }, + ); + + my ($p) = $mech->create_problems_for_body(1, $isleofwight->id, 'Title', { external_id => 1 }); + + my $c = FixMyStreet::App->model('DB::Comment')->create({ + problem => $p, user => $p->user, anonymous => 't', text => 'Update text', + problem_state => 'confirmed', state => 'confirmed', mark_fixed => 0, + confirmed => DateTime->now(), + }); + $c->set_extra_metadata('triage_report', 1); + $c->update; + + my $id = $o->post_service_request_update($c); + is $id, 248, 'correct update ID returned'; + my $cgi = CGI::Simple->new($o->test_req_used->content); + my $name = $p->user->name . ' \(' . $p->user->email . '\)'; + like $cgi->param('description'), qr/^FMS-Update:/, 'FMS update prefix included'; + like $cgi->param('description'), qr/Triaged by $name/, 'Triage user details included'; + + $p->comments->delete; + $p->delete; + }; +}; + my ($p) = $mech->create_problems_for_body(1, $isleofwight->id, '', { cobrand => 'isleofwight' }); my $alert = FixMyStreet::App->model('DB::Alert')->create( { parameter => $p->id, diff --git a/templates/email/isleofwight/other-reported.html b/templates/email/isleofwight/other-reported.html new file mode 100644 index 000000000..8e85c5729 --- /dev/null +++ b/templates/email/isleofwight/other-reported.html @@ -0,0 +1,42 @@ +[% + +email_summary = "Thanks for logging your report"; +email_columns = 2; + +PROCESS '_email_settings.html'; +INCLUDE '_email_top.html'; + +%] + +<th style="[% td_style %][% primary_column_style %]" id="primary_column"> + [% start_padded_box %] + <h1 style="[% h1_style %]">Your report has been logged</h1> + [% IF report.state == 'for triage' %] + <p style="[% p_style %]">Thank you for submitting your report to FixMyStreet, it will be submitted to Island Roads for review.</p> + [% ELSE %] + <p style="[% p_style %]">Thank you, your enquiry has been received by Island Roads and appropriate action will be taken.</p> + + <p style="[% p_style %]">We don't routinely contact customers regarding their enquiries, unless we have a specific query about + the issue. Any status updates for the issue can be tracked via FixMyStreet, if you contact us again about this issue, + please quote your 8-digit reference number.</p> + + <p style="[% p_style %]">Thank you for submitting your enquiry to us via FixMyStreet.</p> + [% END %] + +[% IF cobrand.is_council && !cobrand.owns_problem( report ) %] +<p style="[% p_style %]">Please note that [% cobrand.council_name %] is not responsible for this type +of report, so it will instead be sent to [% report.body %].</p> +[% ELSE %] +[% TRY %][% INCLUDE '_council_reference.html' problem=report %][% CATCH file %][% END %] +[% END %] + <p style="margin: 20px auto; text-align: center"> + <a style="[% button_style %]" href="[% cobrand.base_url_for_report(report) %][% report.url %]">View my report</a> + </p> + [% end_padded_box %] +</th> +[% WRAPPER '_email_sidebar.html' object = report %] + <h2 style="[% h2_style %]">[% report.title | html %]</h2> + <p style="[% secondary_p_style %]">[% report.detail | html %]</p> +[% END %] + +[% INCLUDE '_email_bottom.html' %] diff --git a/templates/email/isleofwight/other-reported.txt b/templates/email/isleofwight/other-reported.txt new file mode 100644 index 000000000..a368bd95e --- /dev/null +++ b/templates/email/isleofwight/other-reported.txt @@ -0,0 +1,43 @@ +Subject: Your report has been logged: [% report.title %] + +Hello [% report.name %], + +[% IF report.state == 'for triage' %] +Thank you for submitting your report to FixMyStreet, it will be +submitted to Island Roads for review. +[% ELSE %] +Thank you, your enquiry has been received by Island Roads and +appropriate action will be taken. + +We don't routinely contact customers regarding their enquiries, +unless we have a specific query about the issue. Any status +updates for the issue can be tracked via FixMyStreet, if you +contact us again about this issue, please quote your 8-digit +reference number. + +Thank you for submitting your enquiry to us via FixMyStreet. +[% END %] + +[% IF cobrand.is_council && !cobrand.owns_problem( report ) %] +Please note that [% cobrand.council_name %] is not responsible for this type +of report, so it will instead be sent to [% report.body %]. +[% ELSE %] +[% TRY %][% INCLUDE '_council_reference.txt' problem=report %][% CATCH file %][% END %] +[% END %] + +It is available to view at: + +[% cobrand.base_url_for_report(report) %][% report.url %] + +Your report has the title: + +[% report.title %] + +And details: + +[% report.detail %] + +[% signature %] + +This email was sent automatically, from an unmonitored email account - so +please do not reply to it. diff --git a/templates/web/base/admin/report-category.html b/templates/web/base/admin/report-category.html index 0416d71c0..64aa474b9 100644 --- a/templates/web/base/admin/report-category.html +++ b/templates/web/base/admin/report-category.html @@ -1,4 +1,7 @@ -<select class="form-control" name="category" id="category"> +[%~ IF NOT select_name %] + [%~ select_name = 'category' %] +[%~ END %] +<select class="form-control" name="[% select_name %]" id="[% select_name %]"> [% IF NOT problem.category OR NOT categories_hash.${problem.category} %] <optgroup label="[% loc('Existing category') %]"> <option selected value="[% problem.category | html %]">[% (problem.category_display OR '-') | html %]</option> diff --git a/templates/web/base/admin/triage/_inspect.html b/templates/web/base/admin/triage/_inspect.html new file mode 100644 index 000000000..926197ceb --- /dev/null +++ b/templates/web/base/admin/triage/_inspect.html @@ -0,0 +1,77 @@ +[% BLOCK category_list %] +<select class="form-control" name="[% field_name %]" id="[% field_name %]"> + [% IF category_options.size %] + [%~ IF category_groups.size ~%] + [%~ FOREACH group IN category_groups ~%] + [% IF group.name %]<optgroup label="[% group.name %]">[% END %] + [% group_select = 0 %] + [% selected = 0 %] + [%~ FOREACH cat_op IN group.categories ~%] + [% IF group_select == 0 AND problem.category == group.name %] + [% selected = 1; group_select = 1 %] + [% END %] + <option value="[% cat_op.category | html %]"[% ' selected' IF selected OR problem.category == cat_op.category %]>[% cat_op.category_display | html %] ([% cat_op.email | html %])</option> + [% selected = 0 %] + [%~ END ~%] + [% IF group.name %]</optgroup>[% END %] + [%~ END =%] + [% ELSE %] + [% FOREACH cat IN category_options %] + <option value="[% cat.category | html %]"[% ' selected' IF problem.category == cat.category %]>[% cat.category_display | html %]</option> + [% END %] + [% END %] + [% END %] +</select> +[% END %] + +[% permissions = c.user.permissions(problem) %] +[% second_column = BLOCK -%] + <div id="side-inspect"> + + <h2 class="inspect-form-heading">[% loc('Inspect report') %]</h2> + + [% INCLUDE 'errors.html' %] + + <form name="report_inspect_form" id="report_inspect_form" method="post" action="[% c.uri_for( '/report', problem.id ) %]" class="validate"> + + [% INCLUDE 'report/inspect/information.html' no_relocate=1 %] + + [% IF permissions.report_edit_category OR permissions.report_inspect OR permissions.triage %] + <div class="inspect-section"> + <p> + <strong>Holding category:</strong> [% problem.category %] + </p> + + <p> + <label for="category">CONFIRM Subject</label> + [% PROCESS category_list category_groups=end_groups field_name="category" categories_hash=end_categories category_options=end_options %] + </p> + + </div> + [% END %] + + <div class="inspect-section"> + [%- extra_fields = problem.get_extra_fields -%] + [% FOR field IN extra_fields %] + [% NEXT IF field.name == 'urgent' %] + <p> + <label for="[% field.name %]">[% field.description %]</label> + <input class="form-control" name="[% field.name %]" type="text" value="[% field.value | html %]" disabled> + </p> + [% END %] + + [% IF permissions.report_inspect OR permissions.triage %] + [% INCLUDE 'report/inspect/public_update.html' public_update_defaulted=0 %] + [% END %] + + <p> + <input type="hidden" name="token" value="[% csrf_token %]" /> + <input class="btn btn-primary" type="submit" value="[% loc('Save changes') %]" data-value-original="[% loc('Save changes') %]" data-value-duplicate="[% loc('Save + close as duplicate') %]" name="save" /> + </p> + </div> + + <input type="hidden" name="js" value=""> + <input type="hidden" name="triage" value="1"> + </form> + </div> +[%- END %] diff --git a/templates/web/base/admin/triage/_list-filters.html b/templates/web/base/admin/triage/_list-filters.html new file mode 100644 index 000000000..f6bedcb80 --- /dev/null +++ b/templates/web/base/admin/triage/_list-filters.html @@ -0,0 +1,53 @@ +[% select_category = BLOCK %] + [% IF filter_categories.size %] + <select class="form-control js-multiple" name="filter_category" id="filter_categories" multiple data-all="[% loc('Everything') %]"> + [% FOR cat IN filter_categories %] + <option value="[% cat.category | html %]"[% ' selected' IF filter_category.${cat.category} %]> + [% cat.category_display | html %] + [%~ IF cat.get_extra_metadata('help_text') %] ([% cat.get_extra_metadata('help_text') %])[% END ~%] + </option> + [% END %] + </select> + [% ELSE %] + [% loc('Everything') %] + [% END %] +[% END %] + +<div class="report-list-filters-wrapper"> + +[% IF use_form_wrapper %] + <form method="get" action=""> +[% END %] + + <p class="report-list-filters"> + [% tprintf(loc('<label for="statuses">Show</label> %s reports <label for="filter_categories">about</label> %s', "The first %s is a dropdown of all/fixed/etc, the second is a dropdown of categories"), 'untriaged', select_category) %] + <input type="submit" name="filter_update" value="[% loc('Go') %]"> + </p> + + <p class="report-list-filters"> + <label for="sort">[% loc('Sort by') %]</label> + <select class="form-control" name="sort" id="sort"> + [% IF shortlist %] + <option value="shortlist"[% ' selected' IF sort_key == 'shortlist' %]>[% loc('Manual order') %]</option> + [% END %] + <option value="created-desc"[% ' selected' IF sort_key == 'created-desc' %]>[% loc('Newest') %]</option> + <option value="created-asc"[% ' selected' IF sort_key == 'created-asc' %]>[% loc('Oldest') %]</option> + <option value="updated-desc"[% ' selected' IF sort_key == 'updated-desc' %]>[% loc('Recently updated') %]</option> + <option value="updated-asc"[% ' selected' IF sort_key == 'updated-asc' %]>[% loc('Least recently updated') %]</option> + <option value="comments-desc"[% ' selected' IF sort_key == 'comments-desc' %]>[% loc('Most commented') %]</option> + </select> + <input type="submit" name="filter_update" value="[% loc('Go') %]"> + </p> + [% IF page == 'around' %] + <p id="show_old_reports_wrapper" class="report-list-filters[% ' hidden' UNLESS num_old_reports > 0 %]"> + <label for="show_old_reports">[% loc('Show older reports') %]</label> + <input type="checkbox" name="show_old_reports" id="show_old_reports" value="1"[% ' checked' IF show_old_reports %]> + <input type="submit" name="filter_update" value="[% loc('Go') %]"> + </p> + [% END %] + +[% IF use_form_wrapper %] + </form> +[% END %] + +</div> diff --git a/templates/web/base/admin/triage/index.html b/templates/web/base/admin/triage/index.html new file mode 100644 index 000000000..f00bbc1fa --- /dev/null +++ b/templates/web/base/admin/triage/index.html @@ -0,0 +1,42 @@ +[% SET body_name = body.name %] +[% IF c.cobrand.moniker == 'hounslow' %] + [% SET body_name = 'Hounslow Highways' %] +[% ELSIF c.cobrand.moniker == 'isleofwight' %] + [% SET body_name = 'Island Roads' %] +[% END %] + +[% + PROCESS "report/photo-js.html"; + PROCESS "maps/${map.type}.html"; + SET bodyclass = 'mappage'; + INCLUDE 'header.html', + title = 'Awaiting triage' + rss = [ tprintf(loc('Problems within %s, %s', "First %s is the body name, second %s the site name"), name, site_name), rss_url ] +%] + +[% map_html %] + +</div> +<div id="map_sidebar"> + <div id="side"> + + <h1 id="reports_heading"> + Awaiting triage + </h1> + +<section class="full-width"> +[% INCLUDE "admin/triage/_list-filters.html", use_form_wrapper = 1 %] +<div class="js-pagination"> +[% INCLUDE 'pagination.html', param = 'p' %] +</div> +<div id="js-reports-list"> + [% INCLUDE 'reports/_problem-list.html' %] +</div> +<div class="js-pagination"> +[% INCLUDE 'pagination.html', param = 'p' %] +</div> +</section> + + </div> +</div> +[% INCLUDE 'footer.html' %] diff --git a/templates/web/base/report/_inspect.html b/templates/web/base/report/_inspect.html index fa79d9912..10f3b6b84 100644 --- a/templates/web/base/report/_inspect.html +++ b/templates/web/base/report/_inspect.html @@ -11,60 +11,7 @@ <form name="report_inspect_form" id="report_inspect_form" method="post" action="[% c.uri_for( '/report', problem.id ) %]" class="validate"> - <div class="inspect-section"> - <p style="float: right"> - <label for="non_public">[% loc('Private') %]</label> - <input type="checkbox" id="non_public" name="non_public" value="1"[% ' checked' IF problem.non_public %]> - </p> - <p> - <strong>[% loc('Report ID:') %]</strong> - <span class="js-report-id">[% problem.id %]</span> - [% IF c.user_exists AND c.cobrand.admin_allow_user(c.user) AND c.user.has_permission_to('report_edit', problem.bodies_str_ids) %] - (<a href="[% c.uri_for_action( "admin/report_edit", problem.id ) %]">[% loc('admin') %]</a>) - [% END %] - </p> - [% IF permissions.report_inspect AND problem.user.phone %] - <p> - <strong>[% loc('Phone Reporter:') %]</strong> - <a href="tel:[% problem.user.phone | html %]">[% problem.user.phone_display | html %]</a> - </p> - [% END %] - <p> - [% SET local_coords = problem.local_coords; %] - [% IF local_coords %] - <strong>[% loc('Easting/Northing:') %]</strong> - <span id="problem_easting">[% local_coords.0 %]</span>, - <span id="problem_northing">[% local_coords.1 %]</span> - [% ELSE %] - <strong>[% loc('Latitude/Longitude:') %]</strong> - <span id="problem_latitude">[% problem.latitude %]</span>, - <span id="problem_longitude">[% problem.longitude %]</span> - [% END %] - <input type="hidden" name="longitude" value="[% problem.longitude %]"> - <input type="hidden" name="latitude" value="[% problem.latitude %]"> - </p> - [% IF problem.nearest_address() %] - <p> - <strong>[% loc('Nearest calculated address:') %]</strong> - [% problem.nearest_address() %] - </p> - [% END %] - <p> - <a target="_blank" href="https://www.google.com/maps/dir/?api=1&destination=[% problem.latitude %],[% problem.longitude %]" class="btn btn--block btn--navigate">[% loc('Navigate to this problem') %]</a> - </p> - <p> - <a href="#" class="btn btn--block btn--geolocate">[% loc('Set to my current location') %]</a> - </p> - [% IF permissions.report_reject %] - [% TRY %] - [% INCLUDE 'report/_inspect_reject_button.html' %] - [% CATCH file %] - <p> - <a href="[% c.uri_for( '/contact', { id => problem.id, reject => 1 } ) %]" class="btn btn--block">[% loc('Reject report') %]</a> - </p> - [% END %] - [% END %] - </div> + [% INCLUDE 'report/inspect/information.html' %] [% IF permissions.report_edit_category OR permissions.report_inspect %] <div class="inspect-section"> @@ -182,17 +129,7 @@ <div class="inspect-section"> [% IF permissions.report_inspect %] - <p> - <label class="label-containing-checkbox"> - <input type="checkbox" name="include_update" value="1" class="js-toggle-public-update" checked> - [% loc('Save with a public update') %] - </label> - </p> - <p> - <label for="public_update">[% loc('Public update:') %]</label> - [% INCLUDE 'admin/response_templates_select.html' for='public_update' %] - <textarea rows="2" name="public_update" id="public_update" class="form-control">[% public_update | html %]</textarea> - </p> + [% INCLUDE 'report/inspect/public_update.html' %] [% END %] [% IF problem.get_extra_metadata('inspected') %] diff --git a/templates/web/base/report/inspect/information.html b/templates/web/base/report/inspect/information.html new file mode 100644 index 000000000..cc8989522 --- /dev/null +++ b/templates/web/base/report/inspect/information.html @@ -0,0 +1,56 @@ + <div class="inspect-section"> + <p style="float: right"> + <label for="non_public">[% loc('Private') %]</label> + <input type="checkbox" id="non_public" name="non_public" value="1"[% ' checked' IF problem.non_public %]> + </p> + <p> + <strong>[% loc('Report ID:') %]</strong> + <span class="js-report-id">[% problem.id %]</span> + [% IF c.user_exists AND c.cobrand.admin_allow_user(c.user) AND c.user.has_permission_to('report_edit', problem.bodies_str_ids) %] + (<a href="[% c.uri_for_action( "admin/report_edit", problem.id ) %]">[% loc('admin') %]</a>) + [% END %] + </p> + [% IF permissions.report_inspect AND problem.user.phone %] + <p> + <strong>[% loc('Phone Reporter:') %]</strong> + <a href="tel:[% problem.user.phone | html %]">[% problem.user.phone_display | html %]</a> + </p> + [% END %] + <p> + [% SET local_coords = problem.local_coords; %] + [% IF local_coords %] + <strong>[% loc('Easting/Northing:') %]</strong> + <span id="problem_easting">[% local_coords.0 %]</span>, + <span id="problem_northing">[% local_coords.1 %]</span> + [% ELSE %] + <strong>[% loc('Latitude/Longitude:') %]</strong> + <span id="problem_latitude">[% problem.latitude %]</span>, + <span id="problem_longitude">[% problem.longitude %]</span> + [% END %] + <input type="hidden" name="longitude" value="[% problem.longitude %]"> + <input type="hidden" name="latitude" value="[% problem.latitude %]"> + </p> + [% IF problem.nearest_address() %] + <p> + <strong>[% loc('Nearest calculated address:') %]</strong> + [% problem.nearest_address() %] + </p> + [% END %] + <p> + <a target="_blank" href="https://www.google.com/maps/dir/?api=1&destination=[% problem.latitude %],[% problem.longitude %]" class="btn btn--block btn--navigate">[% loc('Navigate to this problem') %]</a> + </p> + [% UNLESS no_relocate %] + <p> + <a href="#" class="btn btn--block btn--geolocate">[% loc('Set to my current location') %]</a> + </p> + [% END %] + [% IF permissions.report_reject %] + [% TRY %] + [% INCLUDE 'report/_inspect_reject_button.html' %] + [% CATCH file %] + <p> + <a href="[% c.uri_for( '/contact', { id => problem.id, reject => 1 } ) %]" class="btn btn--block">[% loc('Reject report') %]</a> + </p> + [% END %] + [% END %] + </div> diff --git a/templates/web/base/report/inspect/public_update.html b/templates/web/base/report/inspect/public_update.html new file mode 100644 index 000000000..be07d7b89 --- /dev/null +++ b/templates/web/base/report/inspect/public_update.html @@ -0,0 +1,14 @@ +[% IF NOT public_update_defaulted.defined %] + [% public_update_defaulted = 1 %] +[% END %] + <p> + <label class="label-containing-checkbox"> + <input type="checkbox" name="include_update" value="1" class="js-toggle-public-update"[% ' checked' IF public_update_defaulted %]> + [% loc('Save with a public update') %] + </label> + </p> + <p> + <label for="public_update">[% loc('Public update:') %]</label> + [% INCLUDE 'admin/response_templates_select.html' for='public_update' %] + <textarea rows="2" name="public_update" id="public_update" class="form-control">[% public_update | html %]</textarea> + </p> diff --git a/templates/web/base/report/updates.html b/templates/web/base/report/updates.html index 817cc7eb4..93bae4d64 100644 --- a/templates/web/base/report/updates.html +++ b/templates/web/base/report/updates.html @@ -6,6 +6,9 @@ [%- END %] [%- NEXT %] [%- END %] +[%- IF update.get_extra_metadata('triage_report') == 1 AND ( NOT c.user OR ( NOT c.user.from_body AND NOT c.user.is_superuser ) ) %] + [%- NEXT %] +[%- END %] [% INCLUDE 'report/update.html' %] [% END %] diff --git a/templates/web/isleofwight/main_nav_items.html b/templates/web/isleofwight/main_nav_items.html new file mode 100644 index 000000000..5b794a99d --- /dev/null +++ b/templates/web/isleofwight/main_nav_items.html @@ -0,0 +1,59 @@ +[%~ IF problem ~%] + [%~ INCLUDE navitem uri='/report/new?longitude=' _ problem.longitude _ '&latitude=' _ problem.latitude label=loc('Report another problem here') attrs='class="report-a-problem-btn"' ~%] +[%~ ELSIF latitude AND longitude ~%] + [%~ INCLUDE navitem uri='/report/new?longitude=' _ longitude _ '&latitude=' _ latitude label=loc('Report a problem here') attrs='class="report-a-problem-btn"' ~%] +[%~ ELSIF homepage_template ~%] + [%~ INCLUDE navitem uri='/report' label=loc('Report a problem') attrs='class="report-a-problem-btn"' ~%] +[%~ ELSE ~%] + [%~ INCLUDE navitem uri='/' label=loc('Report a problem') attrs='class="report-a-problem-btn"' ~%] +[%~ END ~%] + +[%~ IF c.user_exists ~%] + [%~ INCLUDE navitem uri='/my' label=loc('Your account') ~%] +[%~ ELSE ~%] + [%~ INCLUDE navitem uri='/auth' label=loc('Sign in') ~%] +[%~ END ~%] + +[%~ IF c.user_exists AND c.user.has_body_permission_to('planned_reports') ~%] + [%~ INCLUDE navitem always_url=1 uri='/my/planned' label=loc('Shortlist') ~%] +[%~ END ~%] + +[%~ IF c.user_exists AND c.user.has_body_permission_to('triage') ~%] + [%~ INCLUDE navitem always_url=1 uri='/admin/triage' label=loc('Awaiting triage') ~%] +[%~ END ~%] + +[%~ UNLESS hide_all_reports_link ~%] + [%~ + IF c.user_exists AND c.user.categories.size; + categories = c.user.categories_string | uri; + cat_suffix = "?filter_category=" _ categories; + END; + + reports_uri = '/reports'; + IF c.cobrand.council_area; + body_name = c.cobrand.council_area | uri; + reports_uri = "${reports_uri}/${body_name}"; + END; + + INCLUDE navitem uri=reports_uri label=loc('All reports') suffix=cat_suffix; + ~%] +[%~ END ~%] + +[%~ + IF pc; + pc_uri = pc | uri; + pc_suffix = "/list?pc=" _ pc_uri; + END; + + INCLUDE navitem uri='/alert' label=loc('Local alerts') suffix=pc_suffix; +~%] + +[%~ INCLUDE navitem uri='/faq' label=loc('Help') ~%] + +[%~ UNLESS hide_privacy_link ~%] + [%~ INCLUDE navitem uri=c.cobrand.privacy_policy_url label=loc('Privacy') liattrs='class="nav-menu__item--privacy"' ~%] +[%~ END ~%] + +[%~ IF c.user_exists AND c.cobrand.admin_allow_user(c.user) ~%] + [%~ INCLUDE navitem uri='/admin' label=loc('Admin') ~%] +[%~ END ~%] diff --git a/templates/web/isleofwight/report/inspect.html b/templates/web/isleofwight/report/inspect.html new file mode 100644 index 000000000..6cecff926 --- /dev/null +++ b/templates/web/isleofwight/report/inspect.html @@ -0,0 +1,10 @@ +[% + SET bodyclass = 'mappage with-actions'; + SET two_column_sidebar = 1; + IF problem.state == 'for triage'; + PROCESS 'admin/triage/_inspect.html'; + ELSE; + PROCESS 'report/_inspect.html'; + END; + INCLUDE 'report/display.html' +%] diff --git a/web/cobrands/fixmystreet/staff.js b/web/cobrands/fixmystreet/staff.js index b074a49f3..17bd54b8b 100644 --- a/web/cobrands/fixmystreet/staff.js +++ b/web/cobrands/fixmystreet/staff.js @@ -261,19 +261,22 @@ fixmystreet.staff_set_up = { if ('geolocation' in navigator) { var el = document.querySelector('.btn--geolocate'); - fixmystreet.geolocate(el, function(pos) { - var latlon = new OpenLayers.LonLat(pos.coords.longitude, pos.coords.latitude); - var bng = latlon.clone().transform( - new OpenLayers.Projection("EPSG:4326"), - new OpenLayers.Projection("EPSG:27700") // TODO: Handle other projections - ); - $("#problem_northing").text(bng.lat.toFixed(1)); - $("#problem_easting").text(bng.lon.toFixed(1)); - $("#problem_latitude").text(latlon.lat.toFixed(6)); - $("#problem_longitude").text(latlon.lon.toFixed(6)); - $inspect_form.find("input[name=latitude]").val(latlon.lat); - $inspect_form.find("input[name=longitude]").val(latlon.lon); - }); + // triage pages may not show geolocation button + if (el) { + fixmystreet.geolocate(el, function(pos) { + var latlon = new OpenLayers.LonLat(pos.coords.longitude, pos.coords.latitude); + var bng = latlon.clone().transform( + new OpenLayers.Projection("EPSG:4326"), + new OpenLayers.Projection("EPSG:27700") // TODO: Handle other projections + ); + $("#problem_northing").text(bng.lat.toFixed(1)); + $("#problem_easting").text(bng.lon.toFixed(1)); + $("#problem_latitude").text(latlon.lat.toFixed(6)); + $("#problem_longitude").text(latlon.lon.toFixed(6)); + $inspect_form.find("input[name=latitude]").val(latlon.lat); + $inspect_form.find("input[name=longitude]").val(latlon.lon); + }); + } } // Make the "Provide an update" form toggleable, hidden by default. diff --git a/web/cobrands/isleofwight/assets.js b/web/cobrands/isleofwight/assets.js index 3df034f0c..a68b0418b 100644 --- a/web/cobrands/isleofwight/assets.js +++ b/web/cobrands/isleofwight/assets.js @@ -97,17 +97,26 @@ fixmystreet.assets.add($.extend(true, {}, defaults, { }) })); +function not_found_msg_update() { + $('.category_meta_message').html('Please select an item or a road/pavement/path on the map »'); + $('.category_meta_message').removeClass('meta-highlight'); + $("input[name=asset_details]").val(''); +} + function found_item(layer, asset) { var id = asset.attributes.central_asset_id || ''; if (id !== '') { var attrib = asset.attributes; var asset_name = attrib.feature_type_name + '; ' + attrib.site_name + '; ' + attrib.feature_location; $('.category_meta_message').html('You have selected ' + asset_name); + $('.category_meta_message').addClass('meta-highlight'); + $("input[name=asset_details]").val(asset_name); } else { - $('.category_meta_message').html('Please select an item or a road/pavement/path on the map »'); + not_found_msg_update(); } } + var point_asset_defaults = $.extend(true, {}, defaults, { snap_threshold: 5, select_action: true, @@ -118,7 +127,7 @@ var point_asset_defaults = $.extend(true, {}, defaults, { found_item(this, asset); }, asset_not_found: function() { - $('.category_meta_message').html('Please select an item or a road/pavement/path on the map »'); + not_found_msg_update(); } } @@ -145,7 +154,7 @@ var line_asset_defaults = $.extend(true, {}, defaults, { if ( fixmystreet.assets.selectedFeature() ) { return; } - $('.category_meta_message').html('Please select an item or a road/pavement/path on the map »'); + not_found_msg_update(); } } }); |