diff options
-rw-r--r-- | CHANGELOG.md | 3 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Controller/Admin.pm | 45 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Controller/Report/New.pm | 18 | ||||
-rw-r--r-- | perllib/FixMyStreet/TestMech.pm | 8 | ||||
-rw-r--r-- | perllib/Open311/GetServiceRequestUpdates.pm | 23 | ||||
-rw-r--r-- | perllib/Open311/PopulateServiceList.pm | 14 | ||||
-rw-r--r-- | t/app/controller/admin.t | 112 | ||||
-rw-r--r-- | t/open311/getservicerequestupdates.t | 30 | ||||
-rw-r--r-- | t/open311/populate-service-list.t | 9 | ||||
-rw-r--r-- | templates/web/base/admin/template_edit.html | 39 | ||||
-rw-r--r-- | templates/web/base/admin/templates.html | 24 | ||||
-rw-r--r-- | templates/web/base/report/new/category.html | 33 | ||||
-rw-r--r-- | templates/web/fixmystreet.com/header.html | 43 | ||||
-rw-r--r-- | templates/web/zurich/admin/templates.html | 28 | ||||
-rw-r--r-- | web/cobrands/fixmystreet/fixmystreet.js | 70 |
15 files changed, 454 insertions, 45 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index e81c1644b..0f950e974 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - Add Expand map toggle to more mobile maps. - Add functionality to have per-body /reports page. - Cobrands can disable sending of moderation emails. #1910 + - Open311 category group support. #1923 - Front end improvements: - SVG assets for core elements like button icons and map controls #1888 - Remove unneeded 2x PNG fallback images. @@ -52,6 +53,8 @@ - Council dashboard CSV export now has token based authentication #1911 - And uses machine-readable dates. #1929 - Consolidate various admin summary statistics page. #1919. + - 'Auto-response' flag on response templates is honoured for fetched + Open311 updates. #1924 - UK: - Use SVG logo, inlined on front page. #1887 - Inline critical CSS on front page. diff --git a/perllib/FixMyStreet/App/Controller/Admin.pm b/perllib/FixMyStreet/App/Controller/Admin.pm index b485ea2dc..e21b0cc2f 100644 --- a/perllib/FixMyStreet/App/Controller/Admin.pm +++ b/perllib/FixMyStreet/App/Controller/Admin.pm @@ -1102,19 +1102,52 @@ sub template_edit : Path('templates') : Args(2) { } } @live_contacts; $c->stash->{contacts} = \@all_contacts; - if ($c->req->method eq 'POST') { + # bare block to use 'last' if form is invalid. + if ($c->req->method eq 'POST') { { if ($c->get_param('delete_template') && $c->get_param('delete_template') eq _("Delete template")) { $template->contact_response_templates->delete_all; $template->delete; } else { + my @live_contact_ids = map { $_->id } @live_contacts; + my @new_contact_ids = grep { $c->get_param("contacts[$_]") } @live_contact_ids; + my %new_contacts = map { $_ => 1 } @new_contact_ids; + for my $contact (@all_contacts) { + $contact->{active} = $new_contacts{$contact->{id}}; + } + $template->title( $c->get_param('title') ); $template->text( $c->get_param('text') ); $template->state( $c->get_param('state') ); - $template->auto_response( $c->get_param('auto_response') ? 1 : 0 ); - $template->update_or_insert; - my @live_contact_ids = map { $_->id } @live_contacts; - my @new_contact_ids = grep { $c->get_param("contacts[$_]") } @live_contact_ids; + $template->auto_response( $c->get_param('auto_response') && $template->state ? 1 : 0 ); + if ($template->auto_response) { + my @check_contact_ids = @new_contact_ids; + # If the new template has not specific categories (i.e. it + # applies to all categories) then we need to check each of those + # category ids for existing auto-response templates. + if (!scalar @check_contact_ids) { + @check_contact_ids = @live_contact_ids; + } + my $query = { + 'auto_response' => 1, + 'contact.id' => [ @check_contact_ids, undef ], + 'me.state' => $template->state, + }; + if ($template->in_storage) { + $query->{'me.id'} = { '!=', $template->id }; + } + if ($c->stash->{body}->response_templates->search($query, { + join => { 'contact_response_templates' => 'contact' }, + })->count) { + $c->stash->{errors} = { + auto_response => _("There is already an auto-response template for this category/state.") + }; + } + } + + last if $c->stash->{errors}; + + $template->update_or_insert; $template->contact_response_templates->search({ contact_id => { '!=' => \@new_contact_ids }, })->delete; @@ -1126,7 +1159,7 @@ sub template_edit : Path('templates') : Args(2) { } $c->res->redirect( $c->uri_for( 'templates', $c->stash->{body}->id ) ); - } + } } $c->stash->{response_template} = $template; diff --git a/perllib/FixMyStreet/App/Controller/Report/New.pm b/perllib/FixMyStreet/App/Controller/Report/New.pm index 7f1de3ed4..f63b0af1e 100644 --- a/perllib/FixMyStreet/App/Controller/Report/New.pm +++ b/perllib/FixMyStreet/App/Controller/Report/New.pm @@ -664,7 +664,7 @@ sub setup_categories_and_bodies : Private { $bodies_to_list{ $contact->body_id } = $contact->body; unless ( $seen{$contact->category} ) { - push @category_options, { name => $contact->category, value => $contact->category_display }; + push @category_options, { name => $contact->category, value => $contact->category_display, group => $contact->get_extra_metadata('group') || '' }; my $metas = $contact->get_metadata_for_input; $category_extras{$contact->category} = $metas if @$metas; @@ -682,9 +682,9 @@ sub setup_categories_and_bodies : Private { if (@category_options) { # If there's an Other category present, put it at the bottom @category_options = ( - { name => _('-- Pick a category --'), value => _('-- Pick a category --') }, + { name => _('-- Pick a category --'), value => _('-- Pick a category --'), group => '' }, grep { $_->{name} ne _('Other') } @category_options ); - push @category_options, { name => _('Other'), value => $seen{_('Other')} } if $seen{_('Other')}; + push @category_options, { name => _('Other'), value => $seen{_('Other')}, group => _('Other') } if $seen{_('Other')}; } $c->cobrand->call_hook(munge_category_list => \@category_options, \@contacts, \%category_extras); @@ -705,6 +705,18 @@ sub setup_categories_and_bodies : Private { $c->stash->{missing_details_bodies} = \@missing_details_bodies; $c->stash->{missing_details_body_names} = \@missing_details_body_names; + + my %category_groups = (); + for my $category (@category_options) { + push @{$category_groups{$category->{group}}}, $category; + } + + my @category_groups = (); + for my $group ( grep { $_ ne _('Other') } sort keys %category_groups ) { + push @category_groups, { name => $group, categories => $category_groups{$group} }; + } + push @category_groups, { name => _('Other'), categories => $category_groups{_('Other')} } if ($category_groups{_('Other')}); + $c->stash->{category_groups} = \@category_groups; } sub setup_report_extra_fields : Private { diff --git a/perllib/FixMyStreet/TestMech.pm b/perllib/FixMyStreet/TestMech.pm index dbfc94286..ac2ac023d 100644 --- a/perllib/FixMyStreet/TestMech.pm +++ b/perllib/FixMyStreet/TestMech.pm @@ -630,6 +630,14 @@ sub delete_defect_type { $defect_type->delete; } +sub delete_response_template { + my $mech = shift; + my $response_template = shift; + + $response_template->contact_response_templates->delete_all; + $response_template->delete; +} + sub create_contact_ok { my $self = shift; my %contact_params = ( diff --git a/perllib/Open311/GetServiceRequestUpdates.pm b/perllib/Open311/GetServiceRequestUpdates.pm index db2a452da..2620b176a 100644 --- a/perllib/Open311/GetServiceRequestUpdates.pm +++ b/perllib/Open311/GetServiceRequestUpdates.pm @@ -103,16 +103,17 @@ sub update_comments { $problem = $self->schema->resultset('Problem')->to_body($body)->search( $criteria ); if (my $p = $problem->first) { - next unless defined $request->{update_id} && defined $request->{description}; + next unless defined $request->{update_id}; my $c = $p->comments->search( { external_id => $request->{update_id} } ); if ( !$c->first ) { + my $state = $self->map_state( $request->{status} ); my $comment = $self->schema->resultset('Comment')->new( { problem => $p, user => $self->system_user, external_id => $request->{update_id}, - text => $request->{description}, + text => $self->comment_text_for_request($request, $p, $state), mark_fixed => 0, mark_open => 0, anonymous => 0, @@ -138,8 +139,6 @@ sub update_comments { # do not change the status of the problem as it's # tricky to determine the right thing to do. if ( $comment->created > $p->lastupdate ) { - my $state = $self->map_state( $request->{status} ); - # don't update state unless it's an allowed state and it's # actually changing the state of the problem if ( FixMyStreet::DB::Result::Problem->visible_states()->{$state} && $p->state ne $state && @@ -180,6 +179,22 @@ sub update_comments { return 1; } +sub comment_text_for_request { + my ($self, $request, $problem, $state) = @_; + + return $request->{description} if $request->{description}; + + if (my $template = $problem->response_templates->search({ + auto_response => 1, + 'me.state' => $state + })->first) { + return $template->text; + } + + print STDERR "Couldn't determine update text for $request->{update_id} (report " . $problem->id . ")\n"; + return ""; +} + sub map_state { my $self = shift; my $incoming_state = shift; diff --git a/perllib/Open311/PopulateServiceList.pm b/perllib/Open311/PopulateServiceList.pm index dbd0d1c68..e8d06efdf 100644 --- a/perllib/Open311/PopulateServiceList.pm +++ b/perllib/Open311/PopulateServiceList.pm @@ -156,7 +156,13 @@ sub _handle_existing_contact { if ( $contact and lc($metadata) eq 'true' ) { $self->_add_meta_to_contact( $contact ); } elsif ( $contact and $contact->extra and lc($metadata) eq 'false' ) { - $contact->update( { extra => undef } ); + $contact->set_extra_fields(); + $contact->update; + } + + if (my $group = $self->_current_service->{group}) { + $contact->set_extra_metadata(group => $group); + $contact->update; } push @{ $self->found_contacts }, $self->_current_service->{service_code}; @@ -182,6 +188,12 @@ sub _create_contact { ); }; + if (my $group = $self->_current_service->{group}) { + $contact->set_extra_metadata(group => $group); + $contact->update; + } + + if ( $@ ) { warn "Failed to create contact for service code " . $self->_current_service->{service_code} . " for body @{[$self->_current_body->id]}: $@\n" if $self->verbose >= 1; diff --git a/t/app/controller/admin.t b/t/app/controller/admin.t index 0be54dbc5..179557976 100644 --- a/t/app/controller/admin.t +++ b/t/app/controller/admin.t @@ -1659,6 +1659,108 @@ subtest "response templates are included on page" => sub { }; }; +subtest "auto-response templates that duplicate a single category can't be added" => sub { + $mech->delete_response_template($_) for $oxfordshire->response_templates; + my $template = $oxfordshire->response_templates->create({ + title => "Report fixed - potholes", + text => "Thank you for your report. This problem has been fixed.", + auto_response => 1, + state => 'fixed - council', + }); + $template->contact_response_templates->find_or_create({ + contact_id => $oxfordshirecontact->id, + }); + is $oxfordshire->response_templates->count, 1, "Initial response template was created"; + + + $mech->log_in_ok( $superuser->email ); + $mech->get_ok( "/admin/templates/" . $oxfordshire->id . "/new" ); + + # This response template has the same category & state as an existing one + # so won't be allowed. + my $fields = { + title => "Report marked fixed - potholes", + text => "Thank you for your report. This pothole has been fixed.", + auto_response => 'on', + state => 'fixed - council', + "contacts[".$oxfordshirecontact->id."]" => 1, + }; + $mech->submit_form_ok( { with_fields => $fields } ); + is $mech->uri->path, '/admin/templates/' . $oxfordshire->id . '/new', 'not redirected'; + $mech->content_contains( 'Please correct the errors below' ); + $mech->content_contains( 'There is already an auto-response template for this category/state.' ); + + is $oxfordshire->response_templates->count, 1, "Duplicate response template wasn't added"; +}; + +subtest "auto-response templates that duplicate all categories can't be added" => sub { + $mech->delete_response_template($_) for $oxfordshire->response_templates; + $oxfordshire->response_templates->create({ + title => "Report investigating - all cats", + text => "Thank you for your report. This problem has been fixed.", + auto_response => 1, + state => 'fixed - council', + }); + is $oxfordshire->response_templates->count, 1, "Initial response template was created"; + + + $mech->log_in_ok( $superuser->email ); + $mech->get_ok( "/admin/templates/" . $oxfordshire->id . "/new" ); + + # There's already a response template for all categories and this state, so + # this new template won't be allowed. + my $fields = { + title => "Report investigating - single cat", + text => "Thank you for your report. This problem has been fixed.", + auto_response => 'on', + state => 'fixed - council', + "contacts[".$oxfordshirecontact->id."]" => 1, + }; + $mech->submit_form_ok( { with_fields => $fields } ); + is $mech->uri->path, '/admin/templates/' . $oxfordshire->id . '/new', 'not redirected'; + $mech->content_contains( 'Please correct the errors below' ); + $mech->content_contains( 'There is already an auto-response template for this category/state.' ); + + + is $oxfordshire->response_templates->count, 1, "Duplicate response template wasn't added"; +}; + +subtest "all-category auto-response templates that duplicate a single category can't be added" => sub { + $mech->delete_response_template($_) for $oxfordshire->response_templates; + my $template = $oxfordshire->response_templates->create({ + title => "Report fixed - potholes", + text => "Thank you for your report. This problem has been fixed.", + auto_response => 1, + state => 'fixed - council', + }); + $template->contact_response_templates->find_or_create({ + contact_id => $oxfordshirecontact->id, + }); + is $oxfordshire->response_templates->count, 1, "Initial response template was created"; + + + $mech->log_in_ok( $superuser->email ); + $mech->get_ok( "/admin/templates/" . $oxfordshire->id . "/new" ); + + # This response template is implicitly for all categories, but there's + # already a template for a specific category in this state, so it won't be + # allowed. + my $fields = { + title => "Report marked fixed - all cats", + text => "Thank you for your report. This problem has been fixed.", + auto_response => 'on', + state => 'fixed - council', + }; + $mech->submit_form_ok( { with_fields => $fields } ); + is $mech->uri->path, '/admin/templates/' . $oxfordshire->id . '/new', 'not redirected'; + $mech->content_contains( 'Please correct the errors below' ); + $mech->content_contains( 'There is already an auto-response template for this category/state.' ); + + is $oxfordshire->response_templates->count, 1, "Duplicate response template wasn't added"; +}; + + + $mech->log_in_ok( $superuser->email ); subtest "response priorities can be added" => sub { @@ -1674,8 +1776,8 @@ subtest "response priorities can be added" => sub { }; $mech->submit_form_ok( { with_fields => $fields } ); - is $oxfordshire->response_priorities->count, 1, "Response template was added to body"; - is $oxfordshirecontact->response_priorities->count, 1, "Response template was added to contact"; + is $oxfordshire->response_priorities->count, 1, "Response priority was added to body"; + is $oxfordshirecontact->response_priorities->count, 1, "Response priority was added to contact"; }; subtest "response priorities can set to default" => sub { @@ -1693,7 +1795,7 @@ subtest "response priorities can set to default" => sub { $mech->submit_form_ok( { with_fields => $fields } ); is $oxfordshire->response_priorities->count, 1, "Still one response priority"; - is $oxfordshirecontact->response_priorities->count, 1, "Still one response template"; + is $oxfordshirecontact->response_priorities->count, 1, "Still one response priority"; ok $oxfordshire->response_priorities->first->is_default, "Response priority set to default"; }; @@ -1710,8 +1812,8 @@ subtest "response priorities are limited by body" => sub { name => "Bromley Cat 0", } ); - is $bromley->response_priorities->count, 1, "Response template was added to Bromley"; - is $oxfordshire->response_priorities->count, 1, "Response template wasn't added to Oxfordshire"; + is $bromley->response_priorities->count, 1, "Response priority was added to Bromley"; + is $oxfordshire->response_priorities->count, 1, "Response priority wasn't added to Oxfordshire"; $mech->get_ok( "/admin/responsepriorities/" . $oxfordshire->id ); $mech->content_lacks( $bromleypriority->name ); diff --git a/t/open311/getservicerequestupdates.t b/t/open311/getservicerequestupdates.t index 9a8db4374..da427e505 100644 --- a/t/open311/getservicerequestupdates.t +++ b/t/open311/getservicerequestupdates.t @@ -20,11 +20,18 @@ my $user = FixMyStreet::DB->resultset('User')->find_or_create( my %bodies = ( 2237 => FixMyStreet::DB->resultset("Body")->create({ name => 'Oxfordshire' }), - 2482 => FixMyStreet::DB->resultset("Body")->new({ id => 2482 }), + 2482 => FixMyStreet::DB->resultset("Body")->create({ name=> 'Bromley', id => 2482 }), 2651 => FixMyStreet::DB->resultset("Body")->new({ id => 2651 }), ); $bodies{2237}->body_areas->create({ area_id => 2237 }); +my $response_template = $bodies{2482}->response_templates->create({ + title => "investigating template", + text => "We are investigating this report.", + auto_response => 1, + state => "investigating" +}); + my $requests_xml = qq{<?xml version="1.0" encoding="utf-8"?> <service_requests_updates> <request_update> @@ -157,6 +164,10 @@ for my $test ( end_state => 'confirmed', }, + # NB because we have an auto-response ResponseTemplate set up for + # the 'investigating' state, this test is also testing that the + # response template isn't used if the update XML has a non-empty + # <description>. { desc => 'investigating status changes problem status', description => 'This is a note', @@ -334,6 +345,18 @@ for my $test ( end_state => 'fixed - council', }, { + desc => 'empty description triggers auto-response template', + description => 'We are investigating this report.', + xml_description => '', + external_id => 638344, + start_state => 'fixed - council', + comment_status => 'INVESTIGATING', + mark_fixed => 0, + mark_open => 0, + problem_state => 'investigating', + end_state => 'investigating', + }, + { desc => 'open status does not re-open hidden report', description => 'This is a note', external_id => 638344, @@ -346,7 +369,7 @@ for my $test ( }, ) { subtest $test->{desc} => sub { - my $local_requests_xml = setup_xml($problem->external_id, $problem->id, $test->{comment_status}); + my $local_requests_xml = setup_xml($problem->external_id, $problem->id, $test->{comment_status}, $test->{xml_description}); my $o = Open311->new( jurisdiction => 'mysociety', endpoint => 'http://example.com', test_mode => 1, test_get_returns => { 'servicerequestupdates.xml' => $local_requests_xml } ); $problem->lastupdate( DateTime->now()->subtract( days => 1 ) ); @@ -762,13 +785,14 @@ foreach my $test ( { done_testing(); sub setup_xml { - my ($id, $id_ext, $status) = @_; + my ($id, $id_ext, $status, $description) = @_; my $xml = $requests_xml; my $updated_datetime = sprintf( '<updated_datetime>%s</updated_datetime>', $dt ); $xml =~ s/UPDATED_DATETIME/$updated_datetime/; $xml =~ s#<service_request_id>\d+</service_request_id>#<service_request_id>$id</service_request_id>#; $xml =~ s#<service_request_id_ext>\d+</service_request_id_ext>#<service_request_id_ext>$id_ext</service_request_id_ext>#; $xml =~ s#<status>\w+</status>#<status>$status</status># if $status; + $xml =~ s#<description>.+</description>#<description>$description</description># if defined $description; return $xml; diff --git a/t/open311/populate-service-list.t b/t/open311/populate-service-list.t index 04740a9e8..7d4f491c6 100644 --- a/t/open311/populate-service-list.t +++ b/t/open311/populate-service-list.t @@ -39,6 +39,15 @@ subtest 'check basic functionality' => sub { my $contact_count = FixMyStreet::DB->resultset('Contact')->search( { body_id => 1 } )->count(); is $contact_count, 3, 'correct number of contacts'; + + for my $test ( + { code => "001", group => "sanitation" }, + { code => "002", group => "street" }, + { code => "003", group => "street" }, + ) { + my $contact = FixMyStreet::DB->resultset('Contact')->search( { body_id => 1, email => $test->{code} } )->first; + is $contact->get_extra->{group}, $test->{group}, "Group set correctly"; + } }; subtest 'check non open311 contacts marked as deleted' => sub { diff --git a/templates/web/base/admin/template_edit.html b/templates/web/base/admin/template_edit.html index 76de70dcc..3e436dbf9 100644 --- a/templates/web/base/admin/template_edit.html +++ b/templates/web/base/admin/template_edit.html @@ -9,20 +9,30 @@ accept-charset="utf-8" class="validate"> + [% IF errors %] + <p class="error">[% loc('Please correct the errors below') %]</p> + [% END %] + + + <div class="admin-hint"> + <p> + [% loc('This is a <strong>private</strong> name for this template so you can identify it when updating reports or editing in the admin.') %] + </p> + </div> <p> <strong>[% loc('Title:') %] </strong> <input type="text" name="title" class="required form-control" size="30" value="[% rt.title| html %]"> </p> + + <div class="admin-hint"> + <p> + [% loc('This is the <strong>public</strong> text that will be shown on the site.') %] + </p> + </div> <p> <strong>[% loc('Text:') %] </strong> <textarea class="form-control" name="text" class="required">[% rt.text |html %]</textarea> </p> - <p> - <label> - <strong>[% loc('Auto-response:') %]</strong> - <input type="checkbox" name="auto_response" [% 'checked' IF rt.auto_response %] /> - </label> - </p> <div class="admin-hint"> <p> @@ -41,13 +51,28 @@ [% INCLUDE 'admin/state_groups_select.html' current_state=rt.state include_empty=1 %] </p> + [% IF errors.auto_response %] + <div class="form-error">[% errors.auto_response %]</div> + [% END %] + <div class="admin-hint"> + <p> + [% loc('If ticked, this template will be used for Open311 updates that put problems in this state.') %] + </p> + </div> + <p> + <label> + <strong>[% loc('Auto-response:') %]</strong> + <input type="checkbox" name="auto_response" [% 'checked' IF rt.auto_response %] /> + </label> + </p> + <p> <input type="hidden" name="token" value="[% csrf_token %]" > <input type="submit" class="btn" name="Edit templates" value="[% rt.id ? loc('Save changes') : loc('Create template') %]" > </p> [% IF rt.id %] <p> - <input class="delete btn-danger" type="submit" name="delete_template" value="[% loc('Delete template') %]"> + <input class="delete btn-danger" type="submit" name="delete_template" value="[% loc('Delete template') %]" data-confirm="[% loc('Are you sure?') %]"> </p> [% END %] </form> diff --git a/templates/web/base/admin/templates.html b/templates/web/base/admin/templates.html index f9dda7a4c..444f2734d 100644 --- a/templates/web/base/admin/templates.html +++ b/templates/web/base/admin/templates.html @@ -1,24 +1,30 @@ [% INCLUDE 'admin/header.html' title=tprintf(loc('Response Templates for %s'), body.name) -%] -[% IF c.cobrand.moniker == 'zurich' %] - <h2> [% tprintf(loc('Response Templates for %s'), body.name) %] </h2> -[% END %] - <table> <thead> <tr> <th> [% loc('Title') %] </th> - <th> [% loc('Text') %] </th> - <th> [% loc('Created') %] </th> + <th> [% loc('Categories') %] </th> + <th> [% loc('State') %] </th> + <th> [% loc('Auto Response') %] </th> <th> </th> </tr> </thead> <tbody> [% FOR t IN response_templates %] <tr> - <td> [% t.title %] </td> - <td> [% t.text %] </td> - <td> [% t.created %] </td> + <td> [% t.title | html %] </td> + <td> + [% UNLESS t.contacts.size %] + <em>[% loc('All categories') %]</em> + [% ELSE %] + [% FOR contact IN t.contacts %] + [% contact.category_display %][% ',' UNLESS loop.last %] + [% END %] + [% END %] + </td> + <td> [% t.state | html %] </td> + <td> [% IF t.auto_response %]X[% END %] </td> <td> <a href="[% c.uri_for('templates', body.id, t.id) %]" class="btn">[% loc('Edit') %]</a> </td> </tr> [% END %] diff --git a/templates/web/base/report/new/category.html b/templates/web/base/report/new/category.html index 16f6113f2..94d5479a6 100644 --- a/templates/web/base/report/new/category.html +++ b/templates/web/base/report/new/category.html @@ -1,19 +1,38 @@ -[% IF category_options.size ~%] +[% IF category_options.size OR category_groups.size ~%] + [%~ BLOCK category_option ~%] + [% cat_op_lc = cat_op.name | lower =%] + <option value='[% cat_op.name | html %]'[% ' selected' IF report.category == cat_op.name || category_lc == cat_op_lc || (category_options.size == 2 AND loop.last) ~%] + >[% IF loop.first %][% cat_op.value %][% ELSE %][% cat_op.value | html %][% END %]</option> + [%~ END ~%] + [% IF category; category_lc = category | lower; END; ~%] <label for='form_category' id="form_category_label"> [%~ loc('Category') ~%] </label>[% =%] - <select class="form-control" name="category" id="form_category" + <select class="form-control[% IF category_groups.size %] js-grouped-select[% END %]" name="category" id="form_category" [%~ IF c.user.from_body =%] data-role="[% c.user.has_body_permission_to('planned_reports') ? 'inspector' : 'user' %]" data-body="[% c.user.from_body.name %]" data-prefill="[% c.cobrand.prefill_report_fields_for_inspector %]" [%~ END ~%] > - [%~ FOREACH cat_op IN category_options ~%] - [% cat_op_lc = cat_op.name | lower =%] - <option value='[% cat_op.name | html %]'[% ' selected' IF report.category == cat_op.name || category_lc == cat_op_lc || (category_options.size == 2 AND loop.last) ~%] - >[% IF loop.first %][% cat_op.value %][% ELSE %][% cat_op.value | html %][% END %]</option> - [%~ END =%] + [%~ IF category_groups.size ~%] + [%~ FOREACH group IN category_groups ~%] + [% IF group.name %]<optgroup label="[% group.name %]">[% END %] + [%~ FOREACH cat_op IN group.categories ~%] + [% INCLUDE category_option %] + [%~ END ~%] + [% IF group.name %]</optgroup>[% END %] + [%~ END =%] + [%~ ELSE ~%] + [%~ FOREACH cat_op IN category_options ~%] + [% INCLUDE category_option %] + [%~ END =%] + [%~ END ~%] </select> + [%~ IF category_groups.size ~%] + <label id="form_subcategory_label" class="hidden"> + [%~ loc('Subcategory') ~%] + </label> + [%~ END ~%] [%~ END ~%] diff --git a/templates/web/fixmystreet.com/header.html b/templates/web/fixmystreet.com/header.html new file mode 100644 index 000000000..6f01f4184 --- /dev/null +++ b/templates/web/fixmystreet.com/header.html @@ -0,0 +1,43 @@ +[% + SET html_att = ' lang="' _ lang_code _ '"'; + # For a right-to-left language, use the following line in your own header: + # SET html_att = html_att _ ' dir="rtl"'; +-%] +<!doctype html> +<!--[if IE 7]> <html class="no-js ie7 iel8"[% html_att %]><![endif]--> +<!--[if IE 8]> <html class="no-js ie8 iel8"[% html_att %]><![endif]--> +<!--[if IE 9]> <html class="no-js ie9"[% html_att %]><![endif]--> +<!--[if gt IE 9]><!--><html class="no-js"[% html_att %] +[% IF appcache ~%] + manifest="/offline/appcache.manifest" +[%~ END %]><!--<![endif]--> + <head> + <meta name="viewport" content="initial-scale=1.0"> + + <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1"> + <meta name="HandHeldFriendly" content="true"> + <meta name="mobileoptimized" content="0"> + + [% INCLUDE 'header_opengraph.html' %] + [% INCLUDE 'header/css.html' %] + [% INCLUDE 'common_header_tags.html' %] + [% TRY %][% PROCESS 'header_extra.html' %][% CATCH file %][% END %] + + </head> + [% TRY %][% PROCESS 'set_body_class.html' %][% CATCH file %][% END %] + <body class="[% bodyclass | html IF bodyclass %]"> + <div class="top_banner top_banner--donate"><p> + <strong>We’re hiring for an exciting new project.</strong> + <a href="https://mysociety.workable.com/">Come and join our friendly team</a>. + </p></div> + + <div class="wrapper"> + <div class="table-cell"> + [% INCLUDE 'header_site.html' %] + + [% pre_container_extra %] + + <div class="container"> + <div class="content[% " $mainclass" | html IF mainclass %]" role="main"> + + <!-- [% INCLUDE 'debug_header.html' %] --> diff --git a/templates/web/zurich/admin/templates.html b/templates/web/zurich/admin/templates.html new file mode 100644 index 000000000..2db9e2e34 --- /dev/null +++ b/templates/web/zurich/admin/templates.html @@ -0,0 +1,28 @@ +[% INCLUDE 'admin/header.html' title=tprintf(loc('Response Templates for %s'), body.name) -%] + +<h2> [% tprintf(loc('Response Templates for %s'), body.name) %] </h2> + +<table> + <thead> + <tr> + <th> [% loc('Title') %] </th> + <th> [% loc('Text') %] </th> + <th> [% loc('Created') %] </th> + <th> </th> + </tr> + </thead> + <tbody> +[% FOR t IN response_templates %] + <tr> + <td> [% t.title %] </td> + <td> [% t.text %] </td> + <td> [% t.created %] </td> + <td> <a href="[% c.uri_for('templates', body.id, t.id) %]" class="btn">[% loc('Edit') %]</a> </td> + </tr> +[% END %] + </tbody> +</table> + +<a href="[% c.uri_for('templates', body.id, 'new') %]" class="btn">[% loc('New template') %]</a> + +[% INCLUDE 'admin/footer.html' %] diff --git a/web/cobrands/fixmystreet/fixmystreet.js b/web/cobrands/fixmystreet/fixmystreet.js index 16ee7b511..d6ea9de18 100644 --- a/web/cobrands/fixmystreet/fixmystreet.js +++ b/web/cobrands/fixmystreet/fixmystreet.js @@ -424,6 +424,75 @@ $.extend(fixmystreet.set_up, { }); }, + category_groups: function() { + var $category_select = $("select#form_category.js-grouped-select"); + if ($category_select.length === 0) { + return; + } + var $group_select = $("<select></select>").addClass("form-control"); + var $subcategory_label = $("#form_subcategory_label"); + var $empty_option = $category_select.find("option").first(); + + $group_select.change(function() { + var subcategory_id = $(this).find(":selected").data("subcategory_id"); + $(".js-subcategory").hide(); + if (subcategory_id === undefined) { + $subcategory_label.addClass("hidden"); + $category_select.val($(this).val()).change(); + } else { + $("#" + subcategory_id).show().change(); + $("#form_subcategory_label").removeClass("hidden"); + } + }); + + var subcategory_change = function() { + $category_select.val($(this).val()).change(); + }; + + var add_optgroup = function(el) { + var $el = $(el); + var $options = $el.find("option"); + + if ($options.length == 1) { + add_option($options.get(0)); + } else { + var label = $el.attr("label"); + var subcategory_id = "subcategory_" + label.replace(/[^a-zA-Z]+/g, ''); + var $opt = $("<option></option>").text(label).val(label); + $opt.data("subcategory_id", subcategory_id); + $group_select.append($opt); + + var $sub_select = $("<select></select>").addClass("form-control js-subcategory"); + $sub_select.attr("id", subcategory_id); + $sub_select.append($empty_option.clone()); + $options.each(function() { + var $newopt = $(this).clone(); + $sub_select.append($newopt); + // Make sure any preselected value is preserved in the new UI: + if ($newopt.attr('selected')) { + $group_select.val(label); + } + }); + $sub_select.hide().insertAfter($subcategory_label).change(subcategory_change); + } + }; + + var add_option = function(el) { + $group_select.append($(el).clone()); + }; + + $category_select.hide(); + $group_select.insertAfter($category_select); + $category_select.find("optgroup, > option").each(function() { + if (this.tagName.toLowerCase() === 'optgroup') { + add_optgroup(this); + } else if (this.tagName.toLowerCase() === 'option') { + add_option(this); + } + }); + $group_select.change(); + }, + hide_name: function() { $('body').on('click', '.js-hide-name', function(e){ e.preventDefault(); @@ -862,6 +931,7 @@ fixmystreet.update_pin = function(lonlat, savePushState) { if (category_select.val() != '-- Pick a category --') { category_select.change(); } + fixmystreet.run(fixmystreet.set_up.category_groups); if (data.contribute_as) { var $select = $('.js-contribute-as'); |