diff --git a/package-lock.json b/package-lock.json index 17b97dd..14992a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2,10 +2,199 @@ "requires": true, "lockfileVersion": 1, "dependencies": { - "bower": { - "version": "1.8.4", - "resolved": "https://registry.npmjs.org/bower/-/bower-1.8.4.tgz", - "integrity": "sha1-54dqB23rgTf30GUl3F6MZtuC8oo=" + "acorn": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", + "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "enhanced-resolve": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-2.3.0.tgz", + "integrity": "sha1-oRXDJQS2MC6Fp2Jp16V8zdli41k=", + "requires": { + "graceful-fs": "^4.1.2", + "memory-fs": "^0.3.0", + "object-assign": "^4.0.1", + "tapable": "^0.2.3" + } + }, + "errno": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", + "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", + "requires": { + "prr": "~1.0.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "memory-fs": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.3.0.tgz", + "integrity": "sha1-e8xrYp46Q+hx1+Kaymrop/FcuyA=", + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" + }, + "prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=" + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "resolve-from": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", + "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=" + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "tapable": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.2.8.tgz", + "integrity": "sha1-mTcqXJmb8t8WCvwNdL7U9HlIzSI=" + }, + "tern": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/tern/-/tern-0.21.0.tgz", + "integrity": "sha1-gJyHqCbhEklDmM+IlPfC0bNGTrc=", + "requires": { + "acorn": "^4.0.9", + "enhanced-resolve": "^2.2.2", + "glob": "^7.1.1", + "minimatch": "^3.0.3", + "resolve-from": "2.0.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" } } } diff --git a/resources/templates/issue-expert/list.html b/resources/templates/issue-expert/list.html index e69de29..629fc72 100644 --- a/resources/templates/issue-expert/list.html +++ b/resources/templates/issue-expert/list.html @@ -0,0 +1,146 @@ +{% extends "base.html" %} +<!-- File list-followuprequests-Followuprequests.html generated 2018-07-17T07:58:11.329Z by adl.to-selmer-templates. +See [Application Description Language](https://github.com/simon-brooke/adl).--> + + +{% block back-links %} +<div> +<div class='back-link-container'> +<a id='prev-selector' class='back-link'> +Previous +</a> +</div> +</div> + +{% endblock %} +{% block big-links %} +<div> +<div class='big-link-container'> +<a id='next-selector' role='button' class='big-link'> +Next +</a> +</div> +{% ifmemberof %} +<div class='big-link-container'> +<a href='{{servlet-context}}/form-followuprequests-Followuprequest' class='big-link'> +Add a new Followuprequest +</a> +</div> +{% endifmemberof %} +</div> + +{% endblock %} +{% block content %} +<form id='list-followuprequests-Followuprequests' class='list' action='{{servlet-context}}/list-followuprequests-Followuprequests' method='POST'> +{% csrf-field %} +<input id='offset' name='offset' type='hidden' value='{{params.offset|default:0}}'/> +<input id='limit' name='limit' type='hidden' value='{{params.limit|default:50}}'/> +<table caption='followuprequests'> +<thead> +<tr> +<th> +Id +</th> +<th> +Elector_id +</th> +<th> +Visit_id +</th> +<th> +Issue_id +</th> +<th> +Method_id +</th> +<th> + +</th> +</tr> +<tr> +<th> +<input id='id' type='text' name='id' value='{{ params.id }}'/> +</th> +<th> +<input id='elector_id' type='text' name='elector_id' value='{{ params.elector_id }}'/> +</th> +<th> +<input id='visit_id' type='text' name='visit_id' value='{{ params.visit_id }}'/> +</th> +<th> +<input id='issue_id' type='text' name='issue_id' value='{{ params.issue_id }}'/> +</th> +<th> +<input id='method_id' type='text' name='method_id' value='{{ params.method_id }}'/> +</th> +<th> +<input type='submit' id='search-widget' value='Search'/> +</th> +</tr> +</thead> +<tbody> +{% for record in records %} +<tr> +<td> +{{ record.id }} +</td> +<td> +<a href='{{servlet-context}}/form-electors-Elector?id={{ record.elector_id }}'> +{{ record.elector_id_expanded }} +</a> +</td> +<td> +<a href='{{servlet-context}}/form-visits-Visit?id={{ record.visit_id }}'> +{{ record.visit_id_expanded }} +</a> +</td> +<td> +<a href='{{servlet-context}}/form-issues-Issue?id={{ record.issue_id }}'> +{{ record.issue_id_expanded }} +</a> +</td> +<td> +<a href='{{servlet-context}}/form-followupmethods-Followupmethod?id={{ record.method_id }}'> +{{ record.method_id_expanded }} +</a> +</td> +<td> +<a href='{{servlet-context}}/issue-expert/followup-request?id={{ record.id }}'> +View +</a> +</td> +</tr> +{% endfor %} +</tbody> +</table> +</form> + +{% endblock %} +{% block extra-script %} + + var form = document.getElementById('list-followuprequests-Followuprequests'); + var ow = document.getElementById('offset'); + var lw = document.getElementById('limit'); + form.addEventListener('submit', function() { + ow.value='0'; + }); + + var prevSelector = document.getElementById('prev-selector'); + if (prevSelector != null) { + prevSelector.addEventListener('click', function () { + if (parseInt(ow.value)===0) { + window.location = '{{servlet-context}}/admin'; + } else { + ow.value=(parseInt(ow.value)-parseInt(lw.value)); + console.log('Updated offset to ' + ow.value); + form.submit(); + } + }); + } + + document.getElementById('next-selector').addEventListener('click', function () { + ow.value=(parseInt(ow.value)+parseInt(lw.value)); + console.log('Updated offset to ' + ow.value); + form.submit(); + }); +{% endblock %} diff --git a/resources/templates/issue-expert/request.html b/resources/templates/issue-expert/request.html index 5e18b1f..51ea0a0 100644 --- a/resources/templates/issue-expert/request.html +++ b/resources/templates/issue-expert/request.html @@ -1,55 +1,157 @@ {% extends "base.html" %} -<!-- File form-followupmethods-Followupmethod.html generated 2018-07-09T16:38:56.477Z by adl.to-selmer-templates. -See [Application Description Language](https://github.com/simon-brooke/adl).--> +<!-- + Form for issue experts to handle a request from an elector. +--> {% block extra-head %} - + {% script "/js/lib/node_modules/simplemde/dist/simplemde.min.js" %} + {% style "/js/lib/node_modules/simplemde/dist/simplemde.min.css" %} {% endblock %} {% block content %} <div id='content' class='edit'> -<form action='{{servlet-context}}/form-followupmethods-Followupmethod' method='POST'> -{% csrf-field %} -<p class='widget'> -<label for='id'> -Id -</label> -{% ifmemberof admin %} -<input id='id' name='id' type='string' value='{{record.id}}' maxlength='' size='16'/> -{% else %} -{% ifmemberof canvassers teamorganisers issueexperts analysts issueeditors admin %} -<span id='id' name='id' class='pseudo-widget disabled'> -{{record.id}} -</span> -{% else %} -<span id='id' name='id' class='pseudo-widget not-authorised'> -You are not permitted to view id of followupmethods -</span> -{% endifmemberof %} -{% endifmemberof %} -</p> -{% ifmemberof admin %} -<p class='widget action-safe'> -<label for='save-button' class='action-safe'> -To save this followupmethods record -</label> -<input id='save-button' name='save-button' class='action-safe' type='submit' value='Save!'/> -</p> -{% endifmemberof %} -{% ifmemberof admin %} -<p class='widget action-dangerous'> -<label for='delete-button' class='action-dangerous'> -To delete this followupmethods record -</label> -<input id='delete-button' name='delete-button' class='action-dangerous' type='submit' value='Delete!'/> -</p> -{% endifmemberof %} -</form> + <form action='{{servlet-context}}/issue-expert/followup-action' method='POST'> + {% csrf-field %} + <input id='id' name='id' type='hidden' value='{{record.id}}'/> + <p class='widget'> + <label for='elector_id'> + Elector + </label> + {% ifmemberof canvassers teamorganisers issueexperts analysts issueeditors admin %} + <span id='elector_id' name='elector_id' class='pseudo-widget disabled'> + {{elector.name}} ({{elector.gender}}) + </span> + {% else %} + <span id='elector_id' name='elector_id' class='pseudo-widget not-authorised'> + You are not permitted to view elector of followuprequests + </span> + {% endifmemberof %} + </p> + <p class='widget'> + <label for='visit'> + Visit + </label> + {% ifmemberof issueexperts analysts issueeditors admin %} + <span id='visit' name='visit' class='pseudo-widget disabled'> + by {{visit.canvasser_id_expanded}} at {{visit.date}} + </span> + {% else %} + <span id='visit_id' name='visit_id' class='pseudo-widget not-authorised'> + You are not permitted to view visit of followuprequests + </span> + {% endifmemberof %} + </p> + <p class='widget'> + <label for='issue_id'> + {{issue.id}} + </label> + {% ifmemberof issueexperts admin %} + <div id="issue-brief"> + {{issue.brief|safe}} + </div> + {% else %} + <span id='issue_id' name='issue_id' class='pseudo-widget not-authorised'> + You are not permitted to view issue of followuprequests + </span> + {% endifmemberof %} + </p> + <p class='widget'> + <label for='method_id'> + {{record.method_id}} + </label> + {% ifmemberof issueexperts admin %} + <span id='method_id' name='method_id' class='pseudo-widget disabled'> + {% ifequal record.method_id "Phone" %}{{elector.phone}}{% endifequal %} + {% ifequal record.method_id "eMail" %}<a href="mailto:{{elector.email}}">{{elector.email}}</a>{% endifequal %} + </span> + {% else %} + <span id='method_id' name='method_id' class='pseudo-widget not-authorised'> + You are not permitted to view method of followuprequests + </span> + {% endifmemberof %} + </p> + {% if actions|length > 0 %} + <p> + <label for='history'> + History + </label> + <table id='history'> + <thead> + <tr> + <th> + Actor + </th> + <th> + Date + </th> + <th> + Closed? + </th> + </tr> + </thead> + <tbody> + {% for action in actions %} + <tr> + <td>{{action.actor}}</td> + <td>{{action.date}}</td> + <td>{{action.closed}}</td> + </tr> + <tr> + <td colspan="3">{{action.notes|safe}}</td> + </tr> + {% endfor %} + </tbody> + </table> + </p> + {% endif %} + <p class='widget'> + <label for='notes'> + Your notes + </label> + {% ifmemberof admin issueexperts %} + <textarea rows='8' cols='60' id='notes' name='notes'> + </textarea> + {% endifmemberof %} + </p> + <p class='widget'> + <label for='closed'> + Has the elector's query been satisfied? + </label> + {% ifmemberof admin issueexperts %} + <input id='closed' name='closed' type='checkbox' maxlength='' size='16'/> + {% endifmemberof %} + </p> + + {% ifmemberof admin issueexperts %} + <p class='widget action-safe'> + <label for='save-button' class='action-safe'> + To save this followuprequests record + </label> + <input id='save-button' name='save-button' class='action-safe' type='submit' value='Save!'/> + </p> + {% endifmemberof %} + </form> </div> {% endblock %} {% block extra-tail %} <script type='text/javascript'> + var simplemde = new SimpleMDE({ + autosave: { + enabled: true, + uniqueId: "adl-generated-{{page}}", + delay: 1000, + }, + indentWithTabs: true, + insertTexts: { + horizontalRule: ["", "\n\n-----\n\n"], + image: [""], + link: ["[", "](http://)"], + table: ["", "\n\n| Column 1 | Column 2 | Column 3 |\n| -------- | -------- | -------- |\n| Text | Text | Text |\n\n"], + }, + showIcons: ["code"], //, "table"], - sadly, markdown-clj does not support tables + spellChecker: true, + status: ["autosave", "lines", "words", "cursor"] + }); </script> - {% endblock %} diff --git a/src/clj/youyesyet/routes/issue_experts.clj b/src/clj/youyesyet/routes/issue_experts.clj index f157133..8a54566 100644 --- a/src/clj/youyesyet/routes/issue_experts.clj +++ b/src/clj/youyesyet/routes/issue_experts.clj @@ -1,6 +1,7 @@ (ns ^{:doc "Routes/pages available to issue experts." :author "Simon Brooke"} youyesyet.routes.issue-experts - (:require [adl-support.utils :refer [safe-name]] + (:require [adl-support.core :as support] + [adl-support.utils :refer [safe-name]] [clojure.java.io :as io] [clojure.string :as s] [clojure.tools.logging :as log] @@ -40,7 +41,7 @@ (defn list-page [request] (layout/render - "auto/list-followuprequests-Followuprequests.html" + "issue-expert/list.html" (:session request) (let [user (:user (:session request))] {:title "Open requests" @@ -49,14 +50,44 @@ (defn followup-request-page [request] - (layout/render - "issue-expert/request.html" - (:session request) - {:title "Open requests" - :user (:user (:session request)) - :request (db/get-followuprequest - db/*db* - {:id (:id (keywordize-keys (:params request)))})})) + (let + [params (support/massage-params + (keywordize-keys (:params request)) + (keywordize-keys (:form-params request)) + #{:id}) + id (:id (keywordize-keys params)) + record (db/get-followuprequest db/*db* {:id id}) + elector (if + record + (first + (db/search-strings-electors + db/*db* {:id (:elector_id record)}))) + visit (if + record + (first + (db/search-strings-visits + db/*db* {:id (:visit_id record)})))] + (layout/render + "issue-expert/request.html" + (:session request) + {:title (str "Request from " (:name elector) " at " (:date visit)) + :user (:user (:session request)) + :visit visit + :actions (map + ;; HTML-ise the notes in each action record + #(merge % {:notes (md-to-html-string (:notes %))}) + (db/list-followupactions-by-followuprequest + db/*db* {:id id})) + :record record + :elector elector + :issue (let + [raw-issue (if + record + (db/get-issue db/*db* {:id (:issue_id record)}))] + (if raw-issue + (merge + raw-issue + {:brief (md-to-html-string (:brief raw-issue))})))}))) (defroutes issue-expert-routes