aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDave Arter <davea@mysociety.org>2016-12-13 16:41:00 +0000
committerDave Arter <davea@mysociety.org>2016-12-13 16:41:00 +0000
commitb8aa0d6da9009dc3182093165df9b1a4c6d7d164 (patch)
treefaac683c3d9a5ec56b9e54d8913742f1169f0f5e
parent93180a49cb9f56cd92c97ea1e22cf5cc9dd7194a (diff)
parentec6389940afce877a0bc7771d11a27ee7183f96a (diff)
Merge branch 'issues/forcouncils/18-merge-duplicate-reports'
-rw-r--r--perllib/FixMyStreet/App/Controller/Report.pm54
-rw-r--r--perllib/FixMyStreet/DB/Result/Problem.pm25
-rw-r--r--t/app/controller/report_display.t50
-rw-r--r--t/app/controller/report_inspect.t57
-rw-r--r--t/app/controller/report_updates.t2
-rw-r--r--t/app/model/problem.t11
-rw-r--r--templates/web/base/report/_inspect.html49
-rw-r--r--templates/web/base/report/_item.html2
-rw-r--r--templates/web/base/report/display.html9
-rw-r--r--templates/web/base/report/duplicate-no-updates.html7
-rw-r--r--templates/web/base/report/updates.html2
-rw-r--r--templates/web/bromley/report/display.html148
-rw-r--r--templates/web/bromley/report/update-form.html137
-rw-r--r--web/cobrands/fixmystreet/fixmystreet.js94
-rw-r--r--web/cobrands/sass/_report_list_pins.scss8
-rw-r--r--web/js/map-OpenLayers.js86
-rw-r--r--web/js/map-google.js3
17 files changed, 519 insertions, 225 deletions
diff --git a/perllib/FixMyStreet/App/Controller/Report.pm b/perllib/FixMyStreet/App/Controller/Report.pm
index 13967601b..f7ccddc70 100644
--- a/perllib/FixMyStreet/App/Controller/Report.pm
+++ b/perllib/FixMyStreet/App/Controller/Report.pm
@@ -231,13 +231,8 @@ sub generate_map_tags : Private {
latitude => $problem->latitude,
longitude => $problem->longitude,
pins => $problem->used_map
- ? [ {
- latitude => $problem->latitude,
- longitude => $problem->longitude,
- colour => $c->cobrand->pin_colour($problem, 'report'),
- type => 'big',
- } ]
- : [],
+ ? [ $problem->pin_data($c, 'report', type => 'big') ]
+ : [],
);
return 1;
@@ -300,6 +295,7 @@ sub action_router : Path('') : Args(2) {
my ( $self, $c, $id, $action ) = @_;
$c->go( 'map', [ $id ] ) if $action eq 'map';
+ $c->go( 'nearby_json', [ $id ] ) if $action eq 'nearby.json';
$c->detach( '/page_error_404_not_found', [] );
}
@@ -318,9 +314,10 @@ sub inspect : Private {
my $valid = 1;
my $update_text;
my $reputation_change = 0;
+ my %update_params = ();
if ($permissions->{report_inspect}) {
- foreach (qw/detailed_information traffic_information/) {
+ foreach (qw/detailed_information traffic_information duplicate_of/) {
$problem->set_extra_metadata( $_ => $c->get_param($_) );
}
@@ -345,6 +342,14 @@ sub inspect : Private {
if ( $problem->state eq 'hidden' ) {
$problem->get_photoset->delete_cached;
}
+ if ( $problem->state eq 'duplicate' && $old_state ne 'duplicate' ) {
+ # If the report is being closed as duplicate, make sure the
+ # update records this.
+ $update_params{problem_state} = "duplicate";
+ }
+ if ( $problem->state ne 'duplicate' ) {
+ $problem->unset_extra_metadata('duplicate_of');
+ }
if ( $problem->state ne $old_state ) {
$c->forward( '/admin/log_edit', [ $problem->id, 'problem', 'state_change' ] );
}
@@ -389,6 +394,7 @@ sub inspect : Private {
state => 'confirmed',
mark_fixed => 0,
anonymous => 0,
+ %update_params,
} );
}
# This problem might no longer be visible on the current cobrand,
@@ -417,6 +423,38 @@ sub map : Private {
}
+sub nearby_json : Private {
+ my ( $self, $c, $id ) = @_;
+
+ $c->forward( 'load_problem_or_display_error', [ $id ] );
+ my $p = $c->stash->{problem};
+ my $dist = 1000;
+
+ my $nearby = $c->model('DB::Nearby')->nearby(
+ $c, $dist, [ $p->id ], 5, $p->latitude, $p->longitude, undef, [ $p->category ], undef
+ );
+ my @pins = map {
+ my $p = $_->problem;
+ my $colour = $c->cobrand->pin_colour( $p, 'around' );
+ [ $p->latitude, $p->longitude,
+ $colour,
+ $p->id, $p->title_safe, 'small', JSON->false
+ ]
+ } @$nearby;
+
+ my $on_map_list_html = $c->render_fragment(
+ 'around/on_map_list_items.html',
+ { on_map => [], around_map => $nearby }
+ );
+
+ my $json = { pins => \@pins };
+ $json->{current} = $on_map_list_html if $on_map_list_html;
+ my $body = encode_json($json);
+ $c->res->content_type('application/json; charset=utf-8');
+ $c->res->body($body);
+}
+
+
=head2 check_has_permission_to
Ensure the currently logged-in user has any of the provided permissions applied
diff --git a/perllib/FixMyStreet/DB/Result/Problem.pm b/perllib/FixMyStreet/DB/Result/Problem.pm
index ab6f20050..ec1534fe9 100644
--- a/perllib/FixMyStreet/DB/Result/Problem.pm
+++ b/perllib/FixMyStreet/DB/Result/Problem.pm
@@ -181,6 +181,7 @@ use namespace::clean -except => [ 'meta' ];
use Utils;
use FixMyStreet::Map::FMS;
use LWP::Simple qw($ua);
+use RABX;
my $IM = eval {
require Image::Magick;
@@ -941,6 +942,7 @@ sub pin_data {
id => $self->id,
title => $opts{private} ? $self->title : $self->title_safe,
problem => $self,
+ type => $opts{type},
}
};
@@ -1032,4 +1034,27 @@ has shortlisted_user => (
},
);
+has duplicate_of => (
+ is => 'ro',
+ lazy => 1,
+ default => sub {
+ my $self = shift;
+ return unless $self->state eq 'duplicate';
+ my $duplicate_of = int($self->get_extra_metadata("duplicate_of") || 0);
+ return unless $duplicate_of;
+ return $self->result_source->schema->resultset('Problem')->search({ id => $duplicate_of })->first;
+ },
+);
+
+has duplicates => (
+ is => 'ro',
+ lazy => 1,
+ default => sub {
+ my $self = shift;
+ my $rabx_id = RABX::serialise( $self->id );
+ my @duplicates = $self->result_source->schema->resultset('Problem')->search({ extra => { like => "\%duplicate_of,$rabx_id%" } })->all;
+ return \@duplicates;
+ },
+);
+
1;
diff --git a/t/app/controller/report_display.t b/t/app/controller/report_display.t
index fb532ddc4..fad8b2bbb 100644
--- a/t/app/controller/report_display.t
+++ b/t/app/controller/report_display.t
@@ -25,31 +25,16 @@ my $dt = DateTime->new(
second => 23
);
-my $report = FixMyStreet::App->model('DB::Problem')->find_or_create(
- {
- postcode => 'SW1A 1AA',
- bodies_str => '2504',
- areas => ',105255,11806,11828,2247,2504,',
- category => 'Other',
- title => 'Test 2',
- detail => 'Test 2 Detail',
- used_map => 't',
- name => 'Test User',
- anonymous => 'f',
- state => 'confirmed',
- confirmed => $dt->ymd . ' ' . $dt->hms,
- lang => 'en-gb',
- service => '',
- cobrand => 'default',
- cobrand_data => '',
- send_questionnaire => 't',
- latitude => '51.5016605453401',
- longitude => '-0.142497580865087',
- user_id => $user->id,
- }
-);
+my $westminster = $mech->create_body_ok(2504, 'Westminster City Council');
+my ($report, $report2) = $mech->create_problems_for_body(2, $westminster->id, "Example", {
+ user => $user,
+ confirmed => $dt->ymd . ' ' . $dt->hms,
+});
+$report->update({
+ title => 'Test 2',
+ detail => 'Test 2 Detail'
+});
my $report_id = $report->id;
-ok $report, "created test report - $report_id";
subtest "check that no id redirects to homepage" => sub {
$mech->get_ok('/report');
@@ -125,6 +110,22 @@ subtest "check owner of report can view non public reports" => sub {
ok $report->update( { non_public => 0 } ), 'make report public';
};
+subtest "duplicate reports are signposted correctly" => sub {
+ $report2->set_extra_metadata(duplicate_of => $report->id);
+ $report2->state('duplicate');
+ $report2->update;
+
+ my $report2_id = $report2->id;
+ ok $mech->get("/report/$report2_id"), "get '/report/$report2_id'";
+ $mech->content_contains('This report is a duplicate');
+ $mech->content_contains($report->title);
+ $mech->log_out_ok;
+
+ $report2->unset_extra_metadata('duplicate_of');
+ $report2->state('confirmed');
+ $report2->update;
+};
+
subtest "test a good report" => sub {
$mech->get_ok("/report/$report_id");
is $mech->uri->path, "/report/$report_id", "at /report/$report_id";
@@ -532,5 +533,6 @@ subtest "Zurich banners are displayed correctly" => sub {
END {
$mech->delete_user('test@example.com');
+ $mech->delete_body($westminster);
done_testing();
}
diff --git a/t/app/controller/report_inspect.t b/t/app/controller/report_inspect.t
index efb32d116..56e6e957f 100644
--- a/t/app/controller/report_inspect.t
+++ b/t/app/controller/report_inspect.t
@@ -21,12 +21,14 @@ my $wodc = $mech->create_body_ok(2420, 'West Oxfordshire District Council', id =
$mech->create_contact_ok( body_id => $wodc->id, category => 'Horses', email => 'horses@example.net' );
-my ($report) = $mech->create_problems_for_body(1, $oxon->id, 'Test', {
+my ($report, $report2) = $mech->create_problems_for_body(2, $oxon->id, 'Test', {
category => 'Cows', cobrand => 'fixmystreet', areas => ',2237,2420',
whensent => \'current_timestamp',
latitude => 51.847693, longitude => -1.355908,
});
my $report_id = $report->id;
+my $report2_id = $report2->id;
+
my $user = $mech->log_in_ok('test@example.com');
$user->update( { from_body => $oxon } );
@@ -93,6 +95,59 @@ FixMyStreet::override_config {
$mech->content_lacks('Invalid location');
};
+ subtest "test duplicate reports are shown" => sub {
+ my $old_state = $report->state;
+ $report->set_extra_metadata('duplicate_of' => $report2->id);
+ $report->state('duplicate');
+ $report->update;
+
+ $mech->get_ok("/report/$report_id");
+ $mech->content_contains($report2->title);
+
+ $mech->get_ok("/report/$report2_id");
+ $mech->content_contains($report->title);
+
+ $report->unset_extra_metadata('duplicate_of');
+ $report->state($old_state);
+ $report->update;
+ };
+
+ subtest "marking a report as a duplicate with update correctly sets update status" => sub {
+ my $old_state = $report->state;
+ $report->comments->delete_all;
+
+ $mech->get_ok("/report/$report_id");
+ $mech->submit_form_ok({ button => 'save', with_fields => { state => 'Duplicate', duplicate_of => $report2->id, public_update => "This is a duplicate.", save_inspected => "1" } });
+ $report->discard_changes;
+
+ is $report->state, 'duplicate', 'report marked as duplicate';
+ is $report->comments->search({ problem_state => 'duplicate' })->count, 1, 'update marking report as duplicate was left';
+
+ $report->update({ state => $old_state });
+ };
+
+ subtest "marking a report as a duplicate doesn't clobber user-provided update" => sub {
+ my $old_state = $report->state;
+ $report->comments->delete_all;
+
+ $mech->get_ok("/report/$report_id");
+ my $update_text = "This text was entered as an update by the user.";
+ $mech->submit_form_ok({ button => 'save', with_fields => {
+ state => 'Duplicate',
+ duplicate_of => $report2->id,
+ public_update => $update_text,
+ save_inspected => "1",
+ }});
+ $report->discard_changes;
+
+ is $report->state, 'duplicate', 'report marked as duplicate';
+ is $report->comments->search({ problem_state => 'duplicate' })->count, 1, 'update marked report as duplicate';
+ $mech->content_contains($update_text);
+ $mech->content_lacks("Thank you for your report. This problem has already been reported.");
+
+ $report->update({ state => $old_state });
+ };
+
foreach my $test (
{ type => 'report_edit_priority', priority => 1 },
{ type => 'report_edit_category', category => 1 },
diff --git a/t/app/controller/report_updates.t b/t/app/controller/report_updates.t
index e1cd0da71..5a88097fa 100644
--- a/t/app/controller/report_updates.t
+++ b/t/app/controller/report_updates.t
@@ -685,6 +685,8 @@ for my $test (
my $meta_state = $test->{meta} || $test->{fields}->{state};
if ( $test->{reopened} ) {
like $update_meta->[0], qr/reopened$/, 'update meta says reopened';
+ } elsif ( $test->{state} eq 'duplicate' ) {
+ like $update_meta->[0], qr/closed as $meta_state$/, 'update meta includes state change';
} else {
like $update_meta->[0], qr/marked as $meta_state$/, 'update meta includes state change';
}
diff --git a/t/app/model/problem.t b/t/app/model/problem.t
index bd7d0e55c..1130078c0 100644
--- a/t/app/model/problem.t
+++ b/t/app/model/problem.t
@@ -763,6 +763,17 @@ subtest 'check response templates' => sub {
is $problem->response_templates, 2, 'Global and pothole templates returned';
};
+subtest 'check duplicate reports' => sub {
+ my ($problem1, $problem2) = $mech->create_problems_for_body(2, $body_ids{2651}, 'TITLE');
+ $problem1->set_extra_metadata(duplicate_of => $problem2->id);
+ $problem1->state('duplicate');
+ $problem1->update;
+
+ is $problem1->duplicate_of->title, $problem2->title, 'problem1 returns correct problem from duplicate_of';
+ is scalar @{ $problem2->duplicates }, 1, 'problem2 has correct number of duplicates';
+ is $problem2->duplicates->[0]->title, $problem1->title, 'problem2 includes problem1 in duplicates';
+};
+
END {
$problem->comments->delete if $problem;
$problem->delete if $problem;
diff --git a/templates/web/base/report/_inspect.html b/templates/web/base/report/_inspect.html
index 012411b7e..ccaa756c5 100644
--- a/templates/web/base/report/_inspect.html
+++ b/templates/web/base/report/_inspect.html
@@ -9,7 +9,7 @@
<div class="inspect-section">
<p>
<strong>[% loc('Report ID:') %]</strong>
- [% problem.id %]
+ <span class="js-report-id">[% problem.id %]</span>
</p>
<p>
[% SET local_coords = problem.local_coords; %]
@@ -63,19 +63,38 @@
[% END %]
[% IF permissions.report_inspect %]
- <p>
- <label for="state">[% loc('State') %]</label>
- [%# XXX this is duplicated from admin/report_edit.html, should be refactored %]
- <select name="state" id="state" class="form-control">
- [% FOREACH group IN state_groups %]
- <optgroup label="[% group.0 %]">
- [% FOREACH state IN group.1 %]
- <option [% 'selected ' IF state == problem.state %] value="[% state %]">[% state_pretty.$state %]</option>
- [% END %]
- </optgroup>
- [% END %]
- </select>
- </p>
+ <p>
+ <label for="state">[% loc('State') %]</label>
+ [%# XXX this is duplicated from admin/report_edit.html, should be refactored %]
+ <select name="state" id="state" class="form-control">
+ [% FOREACH group IN state_groups %]
+ <optgroup label="[% group.0 %]">
+ [% FOREACH state IN group.1 %]
+ <option [% 'selected ' IF state == problem.state %] value="[% state %]">[% state_pretty.$state %]</option>
+ [% END %]
+ </optgroup>
+ [% END %]
+ </select>
+ </p>
+ <div id="js-duplicate-reports" class="[% "hidden" UNLESS problem.duplicate_of %]">
+ <input type="hidden" name="duplicate_of" value="[% problem.duplicate_of.id %]">
+ <p class="[% "hidden" UNLESS problem.duplicate_of %]"><strong>[% loc('Duplicate of') %]</strong></p>
+ <p class="[% "hidden" IF problem.duplicate_of %]">[% loc('Which report is it a duplicate of?') %]</p>
+ <ul class="item-list">
+ [% IF problem.duplicate_of %]
+ [% INCLUDE 'report/_item.html' item_extra_class = 'item-list--reports__item--selected' problem = problem.duplicate_of %]
+ <li class="item-list__item"><a class="btn" href="#" id="js-change-duplicate-report">[% loc('Choose another') %]</a></li>
+ [% END %]
+ </ul>
+ </div>
+ [% IF problem.duplicates.size %]
+ <p><strong>[% loc('Duplicates') %]</strong></p>
+ <ul class="item-list">
+ [% FOR duplicate IN problem.duplicates %]
+ [% INCLUDE 'report/_item.html' problem = duplicate %]
+ [% END %]
+ </ul>
+ [% END %]
[% END %]
</div>
@@ -139,7 +158,7 @@
<p>
<input type="hidden" name="token" value="[% csrf_token %]">
<a class="btn" href="[% c.uri_for( '/report', problem.id ) %]">[% loc('Cancel') %]</a>
- <input class="btn btn-primary" type="submit" value="[% loc('Save changes') %]" name="save" />
+ <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>
diff --git a/templates/web/base/report/_item.html b/templates/web/base/report/_item.html
index 5894c5d81..0f42b00ce 100644
--- a/templates/web/base/report/_item.html
+++ b/templates/web/base/report/_item.html
@@ -1,4 +1,4 @@
-<li class="item-list__item item-list--reports__item [% item_extra_class %]">
+<li class="item-list__item item-list--reports__item [% item_extra_class %]" data-report-id="[% problem.id | html %]">
<a href="[% c.cobrand.base_url_for_report( problem ) %][% problem.url %]">
[% IF problem.photo %]
<img class="img" height="60" width="90" src="[% problem.photos.first.url_fp %]" alt="">
diff --git a/templates/web/base/report/display.html b/templates/web/base/report/display.html
index 1ee5c4636..7c26c4938 100644
--- a/templates/web/base/report/display.html
+++ b/templates/web/base/report/display.html
@@ -40,12 +40,19 @@
[% INCLUDE 'report/banner.html' %]
[% INCLUDE 'report/_main.html' %]
+
+[% IF problem.duplicate_of %]
+ [% INCLUDE 'report/duplicate-no-updates.html' hide_header = 1 %]
+[% END %]
+
[% TRY %][% INCLUDE 'report/_message_manager.html' %][% CATCH file %][% END %]
[% INCLUDE 'report/display_tools.html' %]
[% TRY %][% INCLUDE 'report/sharing.html' %][% CATCH file %][% END %]
[% INCLUDE 'report/updates.html' %]
-[% IF NOT shown_form %]
+[% IF problem.duplicate_of %]
+ [% INCLUDE 'report/duplicate-no-updates.html' %]
+[% ELSIF NOT shown_form %]
[% INCLUDE 'report/update-form.html' %]
[% END %]
diff --git a/templates/web/base/report/duplicate-no-updates.html b/templates/web/base/report/duplicate-no-updates.html
new file mode 100644
index 000000000..eb9ded728
--- /dev/null
+++ b/templates/web/base/report/duplicate-no-updates.html
@@ -0,0 +1,7 @@
+<div>
+ [% UNLESS hide_header %]<h2>[% loc( 'Provide an update') %]</h2>[% END %]
+ <p>[% loc("This report is a duplicate. Please leave updates on the original report:") %]</p>
+ <ul class="item-list">
+ [% INCLUDE 'report/_item.html' item_extra_class = 'item-list__item--with-pin item-list--reports__item--selected' problem = problem.duplicate_of %]
+ </ul>
+</div>
diff --git a/templates/web/base/report/updates.html b/templates/web/base/report/updates.html
index fc2ac6c78..ff48ecbca 100644
--- a/templates/web/base/report/updates.html
+++ b/templates/web/base/report/updates.html
@@ -54,7 +54,7 @@
[%- ELSIF state == 'not responsible' %]
[%- update_state = loc( "marked as not the council's responsibility" ) %]
[%- ELSIF state == 'duplicate' %]
- [%- update_state = loc( 'marked as a duplicate report' ) %]
+ [%- update_state = loc( 'closed as a duplicate report' ) %]
[%- ELSIF state == 'internal referral' %]
[%- update_state = loc( 'marked as an internal referral' ) %]
[%- END %]
diff --git a/templates/web/bromley/report/display.html b/templates/web/bromley/report/display.html
index 9f2089d28..4c1a69bca 100644
--- a/templates/web/bromley/report/display.html
+++ b/templates/web/bromley/report/display.html
@@ -21,147 +21,19 @@
[% INCLUDE 'report/banner.html' %]
[% INCLUDE 'report/_main.html' %]
-[% INCLUDE 'report/display_tools.html' %]
-[% INCLUDE 'report/updates.html' %]
-
-<div id="update_form">
- <h2>[% loc( 'Provide an update') %]</h2>
-
- [% INCLUDE 'errors.html' %]
-
- <form method="post" action="[% c.uri_for( '/report/update' ) %]" name="updateForm" class="validate"[% IF c.cobrand.allow_photo_upload %] enctype="multipart/form-data"[% END %]>
- <input type="hidden" name="token" value="[% csrf_token %]">
- <fieldset>
- <input type="hidden" name="submit_update" value="1">
- <input type="hidden" name="id" value="[% problem.id | html %]">
-
- [% IF c.cobrand.allow_photo_upload %]
- <input type="hidden" name="upload_fileid" value="[% upload_fileid %]">
- <label for="form_photo">
- <span data-singular="[% loc('Photo') %]" data-plural="[% loc('Photos') %]">[% loc('Photo') %]</span>
- </label>
-
- [% IF field_errors.photo %]
- <p class='form-error'>[% field_errors.photo %]</p>
- [% END %]
-
- <div id="form_photos">
- [% IF upload_fileid %]
- <p>[% loc('You have already attached photos to this update. Note that you can attach a maximum of 3 to this update (if you try to upload more, the oldest will be removed).') %]</p>
- [% FOREACH id IN upload_fileid.split(',') %]
- <img align="right" src="/photo/temp.[% id %]" alt="">
- [% END %]
- [% END %]
- <input type="file" name="photo1" id="form_photo">
- <label for="form_photo2">[% loc('Photo') %]</label>
- <input type="file" name="photo2" id="form_photo2">
- <label for="form_photo3">[% loc('Photo') %]</label>
- <input type="file" name="photo3" id="form_photo3">
- </div>
- [% END %]
-
- <label for="form_update">[% loc( 'Update' ) %]</label>
- [% IF field_errors.update %]
- <div class='form-error'>[% field_errors.update %]</div>
- [% END %]
- <textarea class="form-control" rows="7" cols="30" name="update" id="form_update" placeholder="[% loc('Please write your update here') %]" required>[% update.text | html %]</textarea>
-
- <div class="general-notes">
- <p>Please note this comments box can only be used for this report.
- <br><a href="http://www.bromley.gov.uk/report">Report a different issue</a>
- </div>
-
- [% IF c.user && c.user.belongs_to_body( problem.bodies_str ) %]
- <label for="form_state">[% loc( 'State' ) %]</label>
- <select name="state" id="form_state" class="form-control">
- [% FOREACH state IN [ ['confirmed', loc('Open')], ['investigating',
- loc('Investigating')], ['action scheduled', loc('Action Scheduled')],
- ['in progress', loc('In Progress')], ['duplicate', loc('Duplicate')],
- ['unable to fix', loc('Unable to fix')], ['not responsible', loc('Not Responsible')],
- ['fixed', loc('Fixed')] ] %]
- <option [% 'selected ' IF state.0 == problem.state_display %] value="[% state.0 %]">[% state.1 %]</option>
- [% END %]
- </select>
- [% ELSE %]
- [% IF problem.is_fixed AND c.user_exists AND c.user.id == problem.user_id %]
-
- <input type="checkbox" name="reopen" id="form_reopen" value="1"[% ' checked' IF update.mark_open %]>
- <label class="inline" for="form_reopen">[% loc('This problem has not been fixed') %]</label>
-
- [% ELSIF !problem.is_fixed %]
-
- <div class="checkbox-group">
- <input type="checkbox" name="fixed" id="form_fixed" value="1"[% ' checked' IF update.mark_fixed %]>
- <label class="inline" for="form_fixed">[% loc('This problem has been fixed') %]</label>
- </div>
-
- [% END %]
- [% END %]
-
- [% IF c.user_exists %]
-
- [% INCLUDE name %]
-
- <input class="final-submit green-btn js-submit_register" type="submit" name="submit_register" value="[% loc('Post') %]">
+[% IF problem.duplicate_of %]
+ [% INCLUDE 'report/duplicate-no-updates.html' hide_header = 1 %]
+[% END %]
- [% ELSE %]
-
- <label for="form_rznvy">[% loc('Email' ) %]
- <span class="muted">([% loc('We never show your email') %])</span>
- </label>
-
- [% IF field_errors.email %]
- <p class='form-error'>[% field_errors.email %]</p>
- [% END %]
- <input class="form-control" type="email" name="rznvy" id="form_rznvy" value="[% update.user.email | html %]" placeholder="[% loc('Your email address' ) %]" required>
-
- <div id="form_sign_in">
- <p>To submit your update you now need to confirm it either by email or by using a FixMyStreet password.</p>
-
- <div id="form_sign_in_no" class="form-box">
- <h5>Confirm my report by email</h5>
-
- [% INCLUDE name %]
-
- <label for="password_register">[% loc('Password (optional)') %]</label>
-
- <div class="general-notes">
- <p>[% loc('Providing a password is optional, but doing so will allow you to more easily report problems, leave updates and manage your reports.') %]</p>
- </div>
-
- <div class="form-txt-submit-box">
- <input type="password" class="form-control" name="password_register" id="password_register" value="" placeholder="[% loc('Enter a password') %]">
- <input class="green-btn js-submit_register" type="submit" name="submit_register" value="[% loc('Post') %]">
- </div>
- </div>
- <div id="form_sign_in_yes" class="form-box">
- <h5>Confirm my report with my FixMyStreet password</h5>
-
- <label class="hidden-js n" for="password_sign_in">[% loc('Yes I have a password') %]</label>
- [% IF field_errors.password %]
- <p class='form-error'>[% field_errors.password %]</p>
- [% END %]
- <div class="form-txt-submit-box">
- <input type="password" class="form-control" name="password_sign_in" id="password_sign_in" value="" placeholder="[% loc('Your password') %]">
- <input class="green-btn js-submit_sign_in" type="submit" name="submit_sign_in" value="[% loc('Post') %]">
- </div>
-
- <div class="checkbox-group">
- <input type="checkbox" id="remember_me" name="remember_me" value='1'[% ' checked' IF remember_me %]>
- <label class="inline n" for="remember_me">[% loc('Keep me signed in on this computer') %]</label>
- </div>
- </div>
- </div>
-
- [% END %]
-
- <p>Your information will only be used in accordance with our <a href="/faq#privacy">privacy policy</a>.</p>
-
- </fieldset>
- </form>
-</div>
+[% INCLUDE 'report/display_tools.html' %]
+[% INCLUDE 'report/updates.html' %]
+[% IF problem.duplicate_of %]
+ [% INCLUDE 'report/duplicate-no-updates.html' %]
+[% ELSE %]
+ [% INCLUDE 'report/update-form.html' %]
+[% END %]
</div>
[% INCLUDE 'footer.html' %]
diff --git a/templates/web/bromley/report/update-form.html b/templates/web/bromley/report/update-form.html
new file mode 100644
index 000000000..112cb2960
--- /dev/null
+++ b/templates/web/bromley/report/update-form.html
@@ -0,0 +1,137 @@
+<div id="update_form">
+ <h2>[% loc( 'Provide an update') %]</h2>
+
+ [% INCLUDE 'errors.html' %]
+
+ <form method="post" action="[% c.uri_for( '/report/update' ) %]" name="updateForm" class="validate"[% IF c.cobrand.allow_photo_upload %] enctype="multipart/form-data"[% END %]>
+ <input type="hidden" name="token" value="[% csrf_token %]">
+ <fieldset>
+ <input type="hidden" name="submit_update" value="1">
+ <input type="hidden" name="id" value="[% problem.id | html %]">
+
+ [% IF c.cobrand.allow_photo_upload %]
+ <input type="hidden" name="upload_fileid" value="[% upload_fileid %]">
+ <label for="form_photo">
+ <span data-singular="[% loc('Photo') %]" data-plural="[% loc('Photos') %]">[% loc('Photo') %]</span>
+ </label>
+
+ [% IF field_errors.photo %]
+ <p class='form-error'>[% field_errors.photo %]</p>
+ [% END %]
+
+ <div id="form_photos">
+ [% IF upload_fileid %]
+ <p>[% loc('You have already attached photos to this update. Note that you can attach a maximum of 3 to this update (if you try to upload more, the oldest will be removed).') %]</p>
+ [% FOREACH id IN upload_fileid.split(',') %]
+ <img align="right" src="/photo/temp.[% id %]" alt="">
+ [% END %]
+ [% END %]
+ <input type="file" name="photo1" id="form_photo">
+ <label for="form_photo2">[% loc('Photo') %]</label>
+ <input type="file" name="photo2" id="form_photo2">
+ <label for="form_photo3">[% loc('Photo') %]</label>
+ <input type="file" name="photo3" id="form_photo3">
+ </div>
+ [% END %]
+
+ <label for="form_update">[% loc( 'Update' ) %]</label>
+ [% IF field_errors.update %]
+ <div class='form-error'>[% field_errors.update %]</div>
+ [% END %]
+ <textarea class="form-control" rows="7" cols="30" name="update" id="form_update" placeholder="[% loc('Please write your update here') %]" required>[% update.text | html %]</textarea>
+
+ <div class="general-notes">
+ <p>Please note this comments box can only be used for this report.
+ <br><a href="http://www.bromley.gov.uk/report">Report a different issue</a>
+ </div>
+
+ [% IF c.user && c.user.belongs_to_body( problem.bodies_str ) %]
+ <label for="form_state">[% loc( 'State' ) %]</label>
+ <select name="state" id="form_state" class="form-control">
+ [% FOREACH state IN [ ['confirmed', loc('Open')], ['investigating',
+ loc('Investigating')], ['action scheduled', loc('Action Scheduled')],
+ ['in progress', loc('In Progress')], ['duplicate', loc('Duplicate')],
+ ['unable to fix', loc('Unable to fix')], ['not responsible', loc('Not Responsible')],
+ ['fixed', loc('Fixed')] ] %]
+ <option [% 'selected ' IF state.0 == problem.state_display %] value="[% state.0 %]">[% state.1 %]</option>
+ [% END %]
+ </select>
+ [% ELSE %]
+ [% IF problem.is_fixed AND c.user_exists AND c.user.id == problem.user_id %]
+
+ <input type="checkbox" name="reopen" id="form_reopen" value="1"[% ' checked' IF update.mark_open %]>
+ <label class="inline" for="form_reopen">[% loc('This problem has not been fixed') %]</label>
+
+ [% ELSIF !problem.is_fixed %]
+
+ <div class="checkbox-group">
+ <input type="checkbox" name="fixed" id="form_fixed" value="1"[% ' checked' IF update.mark_fixed %]>
+ <label class="inline" for="form_fixed">[% loc('This problem has been fixed') %]</label>
+ </div>
+
+ [% END %]
+ [% END %]
+
+ [% IF c.user_exists %]
+
+ [% INCLUDE name %]
+
+ <input class="final-submit green-btn js-submit_register" type="submit" name="submit_register" value="[% loc('Post') %]">
+
+
+ [% ELSE %]
+
+ <label for="form_rznvy">[% loc('Email' ) %]
+ <span class="muted">([% loc('We never show your email') %])</span>
+ </label>
+
+ [% IF field_errors.email %]
+ <p class='form-error'>[% field_errors.email %]</p>
+ [% END %]
+ <input class="form-control" type="email" name="rznvy" id="form_rznvy" value="[% update.user.email | html %]" placeholder="[% loc('Your email address' ) %]" required>
+
+ <div id="form_sign_in">
+ <p>To submit your update you now need to confirm it either by email or by using a FixMyStreet password.</p>
+
+ <div id="form_sign_in_no" class="form-box">
+ <h5>Confirm my report by email</h5>
+
+ [% INCLUDE name %]
+
+ <label for="password_register">[% loc('Password (optional)') %]</label>
+
+ <div class="general-notes">
+ <p>[% loc('Providing a password is optional, but doing so will allow you to more easily report problems, leave updates and manage your reports.') %]</p>
+ </div>
+
+ <div class="form-txt-submit-box">
+ <input type="password" class="form-control" name="password_register" id="password_register" value="" placeholder="[% loc('Enter a password') %]">
+ <input class="green-btn js-submit_register" type="submit" name="submit_register" value="[% loc('Post') %]">
+ </div>
+ </div>
+ <div id="form_sign_in_yes" class="form-box">
+ <h5>Confirm my report with my FixMyStreet password</h5>
+
+ <label class="hidden-js n" for="password_sign_in">[% loc('Yes I have a password') %]</label>
+ [% IF field_errors.password %]
+ <p class='form-error'>[% field_errors.password %]</p>
+ [% END %]
+ <div class="form-txt-submit-box">
+ <input type="password" class="form-control" name="password_sign_in" id="password_sign_in" value="" placeholder="[% loc('Your password') %]">
+ <input class="green-btn js-submit_sign_in" type="submit" name="submit_sign_in" value="[% loc('Post') %]">
+ </div>
+
+ <div class="checkbox-group">
+ <input type="checkbox" id="remember_me" name="remember_me" value='1'[% ' checked' IF remember_me %]>
+ <label class="inline n" for="remember_me">[% loc('Keep me signed in on this computer') %]</label>
+ </div>
+ </div>
+ </div>
+
+ [% END %]
+
+ <p>Your information will only be used in accordance with our <a href="/faq#privacy">privacy policy</a>.</p>
+
+ </fieldset>
+ </form>
+</div>
diff --git a/web/cobrands/fixmystreet/fixmystreet.js b/web/cobrands/fixmystreet/fixmystreet.js
index 1701c5cd0..cd3b127d6 100644
--- a/web/cobrands/fixmystreet/fixmystreet.js
+++ b/web/cobrands/fixmystreet/fixmystreet.js
@@ -390,6 +390,87 @@ $.extend(fixmystreet.set_up, {
});
},
+ manage_duplicates: function() {
+ // Deal with changes to report state by inspector/other staff, specifically
+ // displaying nearby reports if it's changed to 'duplicate'.
+ function refresh_duplicate_list() {
+ var report_id = $("#report_inspect_form .js-report-id").text();
+ var args = {
+ filter_category: $("#report_inspect_form select#category").val(),
+ latitude: $('input[name="latitude"]').val(),
+ longitude: $('input[name="longitude"]').val()
+ };
+ $("#js-duplicate-reports ul").html("<li>Loading...</li>");
+ var nearby_url = '/report/'+report_id+'/nearby.json';
+ $.getJSON(nearby_url, args, function(data) {
+ var duplicate_of = $("#report_inspect_form [name=duplicate_of]").val();
+ var $reports = $(data.current)
+ .filter("li")
+ .not("[data-report-id="+report_id+"]")
+ .slice(0, 5);
+ $reports.filter("[data-report-id="+duplicate_of+"]").addClass("item-list--reports__item--selected");
+
+ (function() {
+ var timeout;
+ $reports.on('mouseenter', function(){
+ clearTimeout(timeout);
+ fixmystreet.maps.markers_highlight(parseInt($(this).data('reportId'), 10));
+ }).on('mouseleave', function(){
+ timeout = setTimeout(fixmystreet.maps.markers_highlight, 50);
+ });
+ })();
+
+ $("#js-duplicate-reports ul").empty().prepend($reports);
+
+ $reports.find("a").click(function() {
+ var report_id = $(this).closest("li").data('reportId');
+ $("#report_inspect_form [name=duplicate_of]").val(report_id);
+ $("#js-duplicate-reports ul li").removeClass("item-list--reports__item--selected");
+ $(this).closest("li").addClass("item-list--reports__item--selected");
+ return false;
+ });
+
+ show_nearby_pins(data, report_id);
+ });
+ }
+
+ function show_nearby_pins(data, report_id) {
+ var markers = fixmystreet.maps.markers_list( data.pins, true );
+ // We're replacing all the features in the markers layer with the
+ // possible duplicates, but the list of pins from the server doesn't
+ // include the current report. So we need to extract the feature for
+ // the current report and include it in the list of features we're
+ // showing on the layer.
+ var report_marker = fixmystreet.maps.get_marker_by_id(parseInt(report_id, 10));
+ if (report_marker) {
+ markers.unshift(report_marker);
+ }
+ fixmystreet.markers.removeAllFeatures();
+ fixmystreet.markers.addFeatures( markers );
+ }
+
+ function state_change() {
+ // The duplicate report list only makes sense when state is 'duplicate'
+ if ($(this).val() !== "duplicate") {
+ $("#js-duplicate-reports").addClass("hidden");
+ return;
+ } else {
+ $("#js-duplicate-reports").removeClass("hidden");
+ }
+ // If this report is already marked as a duplicate of another, then
+ // there's no need to refresh the list of duplicate reports
+ var duplicate_of = $("#report_inspect_form [name=duplicate_of]").val();
+ if (!!duplicate_of) {
+ return;
+ }
+
+ refresh_duplicate_list();
+ }
+
+ $("#report_inspect_form").on("change.state", "select#state", state_change);
+ $("#js-change-duplicate-report").click(refresh_duplicate_list);
+ },
+
contribute_as: function() {
$('.content').on('change', '.js-contribute-as', function(){
@@ -575,6 +656,18 @@ $.extend(fixmystreet.set_up, {
$("form#report_inspect_form " + selector).removeClass("hidden");
});
+ // The inspect form submit button can change depending on the selected state
+ $("#report_inspect_form [name=state]").change(function(){
+ var state = $(this).val();
+ var $submit = $("#report_inspect_form input[type=submit]");
+ var value = $submit.attr('data-value-'+state);
+ if (value !== undefined) {
+ $submit.val(value);
+ } else {
+ $submit.val($submit.data('valueOriginal'));
+ }
+ }).change();
+
$('.js-toggle-public-update').each(function() {
var $checkbox = $(this);
var toggle_public_update = function() {
@@ -1093,6 +1186,7 @@ fixmystreet.display = {
$twoColReport.appendTo('#map_sidebar');
$('body').addClass('with-actions');
fixmystreet.set_up.report_page_inspect();
+ fixmystreet.set_up.manage_duplicates();
} else {
$sideReport.appendTo('#map_sidebar');
}
diff --git a/web/cobrands/sass/_report_list_pins.scss b/web/cobrands/sass/_report_list_pins.scss
index 74f0a5f90..eaeefbc10 100644
--- a/web/cobrands/sass/_report_list_pins.scss
+++ b/web/cobrands/sass/_report_list_pins.scss
@@ -50,6 +50,14 @@
color: #777;
}
}
+.item-list--reports__item--selected {
+ background: $base_bg;
+
+ a, a:hover, a:focus {
+ background-color: transparent;
+ }
+}
+
.item-list__item--empty {
background: none;
diff --git a/web/js/map-OpenLayers.js b/web/js/map-OpenLayers.js
index a3cefa7da..49801911b 100644
--- a/web/js/map-OpenLayers.js
+++ b/web/js/map-OpenLayers.js
@@ -90,7 +90,8 @@ var fixmystreet = fixmystreet || {};
size: pin[5] || marker_size,
faded: 0,
id: pin[3],
- title: pin[4] || ''
+ title: pin[4] || '',
+ draggable: pin[6] === false ? false : true
});
markers.push( marker );
}
@@ -144,7 +145,7 @@ var fixmystreet = fixmystreet || {};
admin_drag: function(pin_moved_callback, confirm_change) {
confirm_change = confirm_change || false;
var original_lonlat;
- var drag = new OpenLayers.Control.DragFeature( fixmystreet.markers, {
+ var drag = new OpenLayers.Control.DragFeatureFMS( fixmystreet.markers, {
onStart: function(feature, e) {
// Keep track of where the feature started, so we can put it
// back if the user cancels the operation.
@@ -167,12 +168,41 @@ var fixmystreet = fixmystreet || {};
} );
fixmystreet.map.addControl( drag );
drag.activate();
+ },
+
+ // `markers.redraw()` in markers_highlight will trigger an
+ // `overFeature` event if the mouse cursor is still over the same
+ // marker on the map, which would then run markers_highlight
+ // again, causing an infinite flicker while the cursor remains over
+ // the same marker. We really only want to redraw the markers when
+ // the cursor moves from one marker to another (ie: when there is an
+ // overFeature followed by an outFeature followed by an overFeature).
+ // Therefore, we keep track of the previous event in
+ // fixmystreet.latest_map_hover_event and only call markers_highlight
+ // if we know the previous event was different to the current one.
+ // (See the `overFeature` and `outFeature` callbacks inside of
+ // fixmystreet.select_feature).
+
+ markers_highlight: function(problem_id) {
+ for (var i = 0; i < fixmystreet.markers.features.length; i++) {
+ if (typeof problem_id == 'undefined') {
+ // There is no highlighted marker, so unfade this marker
+ fixmystreet.markers.features[i].attributes.faded = 0;
+ } else if (problem_id == fixmystreet.markers.features[i].attributes.id) {
+ // This is the highlighted marker, unfade it
+ fixmystreet.markers.features[i].attributes.faded = 0;
+ } else {
+ // This is not the hightlighted marker, fade it
+ fixmystreet.markers.features[i].attributes.faded = 1;
+ }
+ }
+ fixmystreet.markers.redraw();
}
};
var drag = {
activate: function() {
- this._drag = new OpenLayers.Control.DragFeature( fixmystreet.markers, {
+ this._drag = new OpenLayers.Control.DragFeatureFMS( fixmystreet.markers, {
onComplete: function(feature, e) {
fixmystreet.update_pin( feature.geometry );
}
@@ -195,35 +225,6 @@ var fixmystreet = fixmystreet || {};
fixmystreet.map.setCenter(center, z);
}
- // `markers.redraw()` in markers_highlight will trigger an
- // `overFeature` event if the mouse cursor is still over the same
- // marker on the map, which would then run markers_highlight
- // again, causing an infinite flicker while the cursor remains over
- // the same marker. We really only want to redraw the markers when
- // the cursor moves from one marker to another (ie: when there is an
- // overFeature followed by an outFeature followed by an overFeature).
- // Therefore, we keep track of the previous event in
- // fixmystreet.latest_map_hover_event and only call markers_highlight
- // if we know the previous event was different to the current one.
- // (See the `overFeature` and `outFeature` callbacks inside of
- // fixmystreet.select_feature).
-
- function markers_highlight(problem_id) {
- for (var i = 0; i < fixmystreet.markers.features.length; i++) {
- if (typeof problem_id == 'undefined') {
- // There is no highlighted marker, so unfade this marker
- fixmystreet.markers.features[i].attributes.faded = 0;
- } else if (problem_id == fixmystreet.markers.features[i].attributes.id) {
- // This is the highlighted marker, unfade it
- fixmystreet.markers.features[i].attributes.faded = 0;
- } else {
- // This is not the hightlighted marker, fade it
- fixmystreet.markers.features[i].attributes.faded = 1;
- }
- }
- fixmystreet.markers.redraw();
- }
-
function sidebar_highlight(problem_id) {
if (typeof problem_id !== 'undefined') {
var $a = $('.item-list--reports a[href$="/' + problem_id + '"]');
@@ -505,7 +506,7 @@ var fixmystreet = fixmystreet || {};
overFeature: function (feature) {
if (fixmystreet.latest_map_hover_event != 'overFeature') {
document.getElementById('map').style.cursor = 'pointer';
- markers_highlight(feature.attributes.id);
+ fixmystreet.maps.markers_highlight(feature.attributes.id);
sidebar_highlight(feature.attributes.id);
fixmystreet.latest_map_hover_event = 'overFeature';
}
@@ -513,7 +514,7 @@ var fixmystreet = fixmystreet || {};
outFeature: function (feature) {
if (fixmystreet.latest_map_hover_event != 'outFeature') {
document.getElementById('map').style.cursor = '';
- markers_highlight();
+ fixmystreet.maps.markers_highlight();
sidebar_highlight();
fixmystreet.latest_map_hover_event = 'outFeature';
}
@@ -666,9 +667,9 @@ var fixmystreet = fixmystreet || {};
var href = $('a', this).attr('href');
var id = parseInt(href.replace(/^.*[/]([0-9]+)$/, '$1'));
clearTimeout(timeout);
- markers_highlight(id);
+ fixmystreet.maps.markers_highlight(id);
}).on('mouseleave', '.item-list--reports__item', function(){
- timeout = setTimeout(markers_highlight, 50);
+ timeout = setTimeout(fixmystreet.maps.markers_highlight, 50);
});
})();
});
@@ -888,6 +889,19 @@ OpenLayers.Control.Click = OpenLayers.Class(OpenLayers.Control, {
}
});
+/* Drag handler that allows individual features to disable dragging */
+OpenLayers.Control.DragFeatureFMS = OpenLayers.Class(OpenLayers.Control.DragFeature, {
+ CLASS_NAME: "OpenLayers.Control.DragFeatureFMS",
+
+ overFeature: function(feature) {
+ if (feature.attributes.draggable) {
+ return OpenLayers.Control.DragFeature.prototype.overFeature.call(this, feature);
+ } else {
+ return false;
+ }
+ }
+})
+
OpenLayers.Renderer.SVGBig = OpenLayers.Class(OpenLayers.Renderer.SVG, {
MAX_PIXEL: 15E7,
CLASS_NAME: "OpenLayers.Renderer.SVGBig"
diff --git a/web/js/map-google.js b/web/js/map-google.js
index 4c3f6188e..be2df8502 100644
--- a/web/js/map-google.js
+++ b/web/js/map-google.js
@@ -56,6 +56,9 @@ fixmystreet.maps = {};
google.maps.event.trigger(fixmystreet.map, 'idle');
};
+ // This is a noop on Google Maps right now.
+ fixmystreet.maps.markers_highlight = function() {};
+
function PaddingControl(div) {
div.style.padding = '40px';
}