From: Brian Flowers Date: Sat, 28 Dec 2024 01:39:41 +0000 (-0500) Subject: Updates after initial canvass run X-Git-Url: http://git.slightlycyberpunk.com%2C%20git.slightlycyberpunk.com/git/?a=commitdiff_plain;h=d62a6330d6ac2f507c41fb18b98dbc569951b4de;p=CCCP.git Updates after initial canvass run --- diff --git a/api.php b/api.php index 8d382d3..51298bd 100644 --- a/api.php +++ b/api.php @@ -145,6 +145,7 @@ if( isset($_GET['get']) && // precincts -- csv list of precincts to restrict search to (ex: 0101,0102) // minLat,minLon,maxLat,maxLon -- min/max coordinates to restrict search to // pending -- show only voters not yet contacted +// turfId -- show only voters within this turf } else if( isset($_GET['get']) && $_GET['get'] == "voterLocs") { $params = Array(); $parties = Array(); @@ -200,13 +201,19 @@ if( isset($_GET['get']) && array_push($params, $_POST['maxLon']); } + $turf = " "; + if( isset($_POST['turfId']) && strlen($_POST['turfId']) > 0 ) { + $turf = " AND ST_WITHIN(PointFromText(CONCAT('POINT(',voters.longitude,' ',voters.latitude,')')), (select geometry from turf where id=?)) "; + array_push($params, $_POST['turfId']); + } + // GOAL: (Optionally) remove all contacted voters -- need to join with canvassing results (currently this is done in a second query below) // Return location along with count of voters of each party/type...or just icon? // These may need to alter how we account for 3k max // Return an object: {"count": nnn, "overflow": true, voterLocs: [ {"display": {"history": non, "party": "Democrat"}, "lat": ..., "lon": ..., "address": ..., "count": ... } ] - $query = "SELECT * FROM (SELECT voters.id as id, address.id as addressId, address.latitude,address.longitude,address.precinct,party,voters.birthyear,address.addressLine1, ". + $query = "SELECT DISTINCT * FROM (SELECT voters.id as id, address.id as addressId, address.latitude,address.longitude,address.precinct,party,voters.birthyear,address.addressLine1, ". (sizeof($lists) > 0 ? "list.icon, cv.listId, " : ""). "(SELECT sum(voted) FROM voterHistory vh WHERE vh.voterId = voters.id) as voteCount ". "FROM voters ". @@ -227,7 +234,8 @@ if( isset($_GET['get']) && "))) ". $precincts. $latlon. - "LIMIT 3000;"; + $turf. + "LIMIT 10000;"; //echo $query.PHP_EOL; //print_r( $params ); @@ -242,28 +250,32 @@ if( isset($_GET['get']) && // Reformat the results for display $output = new stdClass; $output->count = sizeOf($rows); - $output->overflow = $output->count==3000; + $output->overflow = $output->count==10000; $output->voterLocs = Array(); for($i = 0; $i < sizeOf($rows); $i++) { $added = 0; - // Exclude any that have already been canvassed - if(isset($_POST['pending'])) { - $cquery = "SELECT * FROM canvassResults WHERE voterId = ?;"; - $stmt = $dbh->prepare($cquery); - $stmt->execute(Array($rows[$i]['id'])); - $crows = $stmt->fetch(PDO::FETCH_ASSOC); + // Flag/exclude any that have already been canvassed +// if(isset($_POST['pending'])) { + $cquery = "SELECT * FROM canvassResults WHERE voterId = ?;"; + $stmt = $dbh->prepare($cquery); + $stmt->execute(Array($rows[$i]['id'])); + $crows = $stmt->fetch(PDO::FETCH_ASSOC); - if($crows != null) { + $contacted = 0; + if($crows != null) { + if(isset($_POST['pending'])) { continue; } + $contacted = sizeof($crows); } +// } // Set voter history flags if($rows[$i]["voteCount"] >= 2) { $history = "likely"; } else if($rows[$i]["voteCount"] > 0 && $rows[$i]["voteCount"] < 2) { - $history = "likely"; + $history = "unlikely"; } else if((int)$rows[$i]["birthyear"] > 2000) { $history = "new"; } else { @@ -283,13 +295,16 @@ if( isset($_GET['get']) && $output->voterLocs[$j]->parties[$rows[$i]['party']] = 1; } $output->voterLocs[$j]->parties[$rows[$i]['party']]++; - if( !isset($output->voterLocs[$j]->histories[$history]) ) { + if( !isset($output->voterLocs[$j]->histories[$history]) ) { $output->voterLocs[$j]->histories[$history] = 1; } $output->voterLocs[$j]->histories[$history]++; if( $icon != "" ) { $output->voterLocs[$j]->icon = $icon; } + if($contacted == 0) { + $output->voterLocs[$j]->contacted = 0; + } $added = 1; } } @@ -303,6 +318,7 @@ if( isset($_GET['get']) && $outputObj->parties[$rows[$i]['party']] = 1; $outputObj->histories = Array(); $outputObj->histories[$history] = 1; + $outputObj->contacted = $contacted; if( $icon != "" ) { $outputObj->icon = $icon; } @@ -396,7 +412,7 @@ if( isset($_GET['get']) && ") OR (list.id IN (".str_repeat("?,",sizeof($lists)-1)."?) AND list.id = cv.listId AND (cv.voterId = voters.id AND cv.addressId = voterAddresses.id) " : ""). "))); ";*/ - $query = "SELECT * FROM (SELECT voters.id as id, voters.firstName, voters.middleName, voters.lastName, voters.birthyear, party, ". + $query = "SELECT DISTINCT * FROM (SELECT voters.id as id, voters.firstName, voters.middleName, voters.lastName, voters.birthyear, party, ". "address.id as addressId, address.latitude,address.longitude,address.precinct,address.addressLine1, address.addressLine2, ". (sizeof($lists) > 0 ? "list.icon, cv.listId, " : ""). "(SELECT sum(voted) FROM voterHistory vh WHERE vh.voterId = voters.id) as voteCount ". @@ -417,9 +433,7 @@ if( isset($_GET['get']) && ""). "))) ". $precincts. - "LIMIT 3000;"; - - + "LIMIT 10000;"; $stmt = $dbh->prepare($query); $stmt->execute($params); @@ -569,10 +583,10 @@ if( isset($_GET['get']) && // turfPoints -- a list of latitude/longitude points defining the turf area } else if( isset($_GET['set']) && $_GET['set'] == "turf" ) { $name = $_POST['name']; - $points = $_POST['turfPoints']; - $query = "INSERT INTO turf(name, json) VALUES(?,?);"; + $json = $_POST['turfPoints']; + $query = "INSERT INTO turf(name, json, geometry) VALUES(?,?, ST_GeomFromGeoJSON(?));"; $stmt = $dbh->prepare($query); - $stmt->execute([$name, $points]); + $stmt->execute([$name, $json, $json]); $id = $dbh->lastInsertId(); echo '{"status": "OK", "id": '.$id.'}'; @@ -606,7 +620,7 @@ if( isset($_GET['get']) && // Fetch the canvasses in this turf - $query = "SELECT c.id, c.turfId, c.name, c.start, c.end, c.totalContacts, c.lastLoc, c.voterString, ". + $query = "SELECT c.id, c.turfId, c.name, c.start, c.end, c.totalContacts, c.lastLoc, c.voterString, c.script, ". "(SELECT count(distinct voterId) FROM canvassResults r WHERE r.canvassId = c.id) as madeContacts, ". "(SELECT max(timestamp) FROM canvassResults r WHERE r.canvassId = c.id) as lastActive ". "FROM canvasses c WHERE turfId = CAST(? AS INTEGER);"; @@ -653,8 +667,20 @@ if( isset($_GET['get']) && $rows[$i]['party'] = $irow['party']; $rows[$i]['address'] = $irow['addressLine1']." ".$irow['addressLine2']; $rows[$i]['city'] = $irow['city']; + + $rows[$i]['json'] = json_decode($rows[$i]['json']); } + $query = "SELECT script FROM canvasses WHERE id=?;"; + $params= [$id]; + $stmt = $dbh->prepare($query); + $stmt -> execute($params); + $scripts = $stmt->fetchAll(PDO::FETCH_ASSOC); + + + $output = new stdClass(); + $output->dataset = $rows; + $output->script = $scripts[0]['script']; if(isset($_GET['format']) && $_GET['format'] == "csv") { header('Content-Disposition: attachment; filename="canvassResults_'.$id.'.csv";'); echo "result".str_replace("voterId", "voter", implode(",", array_keys($rows[0]))).PHP_EOL; @@ -662,7 +688,7 @@ if( isset($_GET['get']) && echo implode(",", $rows[$i]).PHP_EOL; } } else { - echo json_encode($rows); + echo json_encode($output); } // URL: ?set=canvass @@ -746,19 +772,20 @@ if( isset($_GET['get']) && $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); $minLat = null; $minLon = null; $maxLat = null; $maxLon = null; $row = json_decode($rows[0]['json']); + $row = $row->geometry->coordinates[0]; for($i = 0; $i < sizeof($row); $i++) { if($minLat == null || $minLat > $row[$i]->lat) { - $minLat = $row[$i]->lat; + $minLat = $row[$i][1]; } if($maxLat == null || $maxLat < $row[$i]->lat) { - $maxLat = $row[$i]->lat; + $maxLat = $row[$i][1]; } if($minLon == null || $minLon > $row[$i]->lng) { - $minLon = $row[$i]->lng; + $minLon = $row[$i][0]; } if($maxLon == null || $maxLon < $row[$i]->lng) { - $maxLon = $row[$i]->lng; + $maxLon = $row[$i][0]; } } $latlon = " AND address.latitude > ? AND address.latitude < ?". @@ -837,13 +864,15 @@ if( isset($_GET['get']) && // state // zip } else if( isset($_GET['set']) && $_GET['set'] == "canvassResult") { + // TODO: Maintain prior json? $corrections = $_POST["corrections"] == "true" ? 1 : 0; $dnc = $_POST["dnc"] == "true" ? 1 : 0; $id = $_POST['id']; $canvassId = $_POST['canvassId']; $notes = $_POST['notes']; $priority = $_POST['priority'] == "true" ? 1 : 0; - $supportPct = $_POST['supportPct']; +// $supportPct = $_POST['supportPct']; + $json = $_POST['prompts']; if($notes == "") { $notes = " "; } @@ -864,7 +893,8 @@ if( isset($_GET['get']) && $state = isset($_POST['state']) ? $_POST['state'] : ""; $zip = isset($_POST['zip']) ? $_POST['zip'] : ""; $precinct= isset($_POST['precinct']) ? $_POST['precinct'] : ""; - $query = "INSERT INTO voterAddress(latitude, longitude, address, city, state, zip, precinct) ". + $byear = date('Y', strtotime($bdate)); + $query = "INSERT INTO voterAddresses(latitude, longitude, addressLine1, city, state, zip, precinct) ". "VALUES(?, ?, ?, ?, ?, ?, ?); "; $params = [$lat, $lon, $address, $city, $state, $zip, $precinct]; $stmt = $dbh->prepare($query); @@ -875,9 +905,9 @@ if( isset($_GET['get']) && } $aid = $dbh->lastInsertId(); - $query = "INSERT INTO voter(firstName, middleName, lastName, birthdate, sex, age, phone, party, addressId) ". - "VALUES(?,?,?,?,?,?,?,?,?); "; - $params = [$fname, $mname, $lname, $bdate, $sex, $age, $phone, $party, $aid]; + $query = "INSERT INTO voters(firstName, middleName, lastName, birthyear, phone, party, addressId) ". + "VALUES(?,?,?,?,?,?,?); "; + $params = [$fname, $mname, $lname, $byear, $phone, $party, $aid]; $stmt = $dbh->prepare($query); $stmt->execute($params); if( $dbh->errorInfo()[0] != "00000") { @@ -888,9 +918,9 @@ if( isset($_GET['get']) && } $query = "INSERT INTO canvassResults(voterId, canvassId, timestamp, userId, contactStatus, ". - "contactMethod, estSupportPct, notes, correctionsNeeded, priority, noContact) ". + "contactMethod, notes, correctionsNeeded, priority, noContact, json) ". "VALUES(?, ?, CURRENT_TIMESTAMP, ?, ?, ?, ?, ?, ?, ?, ?);"; - $params = [$id, $canvassId, time(), $_SESSION['userId'], "Canvass", $supportPct, $notes, $corrections, $priority, $dnc]; + $params = [$id, $canvassId, $_SESSION['userId'], "1", "Canvass", $notes, $corrections, $priority, $dnc, $json]; $stmt = $dbh->prepare($query); $stmt->execute($params); print_r(json_encode($dbh->errorInfo())); diff --git a/canvass.php b/canvass.php index b820b68..a023b0c 100644 --- a/canvass.php +++ b/canvass.php @@ -20,14 +20,16 @@ $dbh = new PDO("mysql:host=localhost;dbname=CCCP", "root", "yix", $options); * If not, see . */ --> + + + - 0) - Home | Canvassing + + Home | Canvassing + + -
+Canvassing +
+ + Canvassing
@@ -156,7 +162,7 @@ EOF;
-
+
diff --git a/css/common.css b/css/common.css index 25f41bb..42fc720 100644 --- a/css/common.css +++ b/css/common.css @@ -14,12 +14,54 @@ * If not, see . */ +/* prevent pull-to-refresh for Safari 16+ */ +@media screen and (pointer: coarse) { + @supports (-webkit-backdrop-filter: blur(1px)) and (overscroll-behavior-y: none) { + html { + min-height: 100.3%; + overscroll-behavior-y: none; + } + } +} +/* prevent pull-to-refresh for Safari 9~15 */ +@media screen and (pointer: coarse) { + @supports (-webkit-backdrop-filter: blur(1px)) and (not (overscroll-behavior-y: none)) { + html { + height: 100%; + overflow: hidden; + } + body { + margin: 0px; + max-height: 100%; /* or `height: calc(100% - 16px);` if body has default margin */ + overflow: auto; + -webkit-overflow-scrolling: touch; + } + /* in this case to disable pinch-zoom, set `touch-action: pan-x pan-y;` on `body` instead of `html` */ + } +} -body { +/* prevent pull-to-refresh for Chrome 63+ */ +body{ + overscroll-behavior-y: none; } -#breadcrumb { -/* display: block; +/*body { + overscroll-behavior: none; +}*/ + +/*#breadcrumb { + font-weight: bold; + color: #FEE; + background-color: #000; + display: block; + margin: -.5em -1em; + padding: .4em 2em; + border-bottom:1px solid #FEE; +}*/ + + +/*#breadcrumb { + display: block; margin: 0.5em 0em; font-size: 1.5em; font-weight: bold; @@ -27,10 +69,10 @@ body { border: 2px solid black; background-color: #FCC; border-radius: 0.5em; - padding: 0.5em 1em;*/ + padding: 0.5em 1em; font-weight: bold; color: #FEE; -} +}*/ #breadcrub a:visited { color: #CCC; @@ -84,13 +126,13 @@ body { left: 0%; } -#details { +#details.canvass { display: none; height: 100%; - width: 50%; + width: 100%; position: fixed; top: 0px; - left: 50%; + left: 0%; z-index: 0; font-size: 2em; padding: 1em; @@ -219,7 +261,60 @@ body { vertical-align: middle; } -@media screen and (orientation: portrait), (max-width: 600px) { +.category { + position: relative; + width: 100%; + display: inline-block; + text-align: left; + border-radius: .2em; + margin-top: 1em; + padding: 0em .5em; + font-weight: bold; + text-decoration: none; + color: #FEE; + font-size: 2em; +/* background-color: #844;*/ + border-top: 2px solid white; + box-sizing: border-box; +} + +.category .toggle { + font-size: 0.8em; + display: inline-block; + border: 1px solid black; + border-radius: 0.2em; + width: 1em; + height: 0.8em; + padding-bottom: 0.2em; + margin-right: 0.5em; + text-align: center; + line-height: 1; +/* background-color: #866;*/ + background-color: #FCC; + color: #300; +} + +.category .toggle:hover { + background-color: #800; +} + +.category .list { + position: relative; + font-size: 0.7em; + margin: 0em; + padding: 1em; +} + +.category .list input { + vertical-align: middle; +} + +.category table, .category tr { + width: 100%; +} + + +@media screen and (orientation: portrait), (max-width: 600px), (max-device-width: 800px) { #controls { position: fixed; width: calc(100% - 6em); @@ -246,6 +341,27 @@ body { height: calc(100% /*- 12em*/); z-index: 0; } + + .category { + font-size: 5vw !important; + } + + a.category { + font-size: 5vw !important; + } + + #breadcrumb { + font-size: 3vw; + } + + .loadingIndicator { + font-size: 3vw; + } + + /* Limit Safari zooming on textareas */ + textarea { + font-size: 16px; + } } .dataset { @@ -275,21 +391,20 @@ th { background-color: #FCC; }*/ -.category { +/*.category { position: relative; width: 100%; display: inline-block; text-align: left; border-radius: .2em; - font-size: 2em; margin-top: 1em; padding: 0em .5em; font-weight: bold; text-decoration: none; color: #FEE; - font-size: 2em; + font-size: 2em;*/ /* background-color: #844;*/ - border-top: 2px solid white; +/* border-top: 2px solid white; box-sizing: border-box; } @@ -303,9 +418,9 @@ th { padding-bottom: 0.2em; margin-right: 0.5em; text-align: center; - line-height: 1; + line-height: 1;*/ /* background-color: #866;*/ - background-color: #FCC; +/* background-color: #FCC; color: #300; } @@ -326,7 +441,7 @@ th { .category table, .category tr { width: 100%; -} +}*/ .subcategory { margin-left: 1em; @@ -387,28 +502,18 @@ th { text-align: center; } -#canvassToolbar button { - max-width: 15%; - max-height: 15vw; - background-color: #FEE; - border-radius: 1em; - padding: 5px; - margin: 2px; - transition: width 1s; -} - #canvassToolbar img { max-width: calc(15vw - 10px); max-height: calc(15vw - 10px); } -#geocoderAddrInput, #geocoderAddrInputSubmit { +/*#geocoderAddrInput, #geocoderAddrInputSubmit { width: 0px; - height: 3em; + height: 8em; padding: 0px; margin: 0px; transition: width 1s; -} +}*/ #canvassToolbar.addrInput button, #canvassToolbar.addrInput button img { width: 0px; diff --git a/css/login.css b/css/login.css index a680ed8..7fee818 100644 --- a/css/login.css +++ b/css/login.css @@ -22,6 +22,10 @@ body { margin-top: 1em; } +#loginPrompt label, #loginPrompt input { + font-size: 3em; +} + #loginPrompt input[type=submit] { display: block; width: 50%; diff --git a/css/pane1.css b/css/pane1.css index 0123b93..6a9ac5a 100644 --- a/css/pane1.css +++ b/css/pane1.css @@ -28,6 +28,11 @@ body { border-bottom:1px solid #FEE; } +#username { + position: absolute; + right: 1em; +} + a { color: #FEE; } @@ -36,12 +41,12 @@ a:visited { color: #EEF; } -a.category { +/*a.category { text-decoration: underline; padding-top: 0.25em; margin-bottom: -0.5em; padding-left: 1em; -} +}*/ #toastDiv { position: fixed; @@ -123,7 +128,7 @@ a.category { left: 0px; } } - +/* .category { position: relative; width: 100%; @@ -158,7 +163,7 @@ a.category { .category .toggle:hover { background-color: #800; -} +}*/ .category .list { position: relative; diff --git a/css/pane2.css b/css/pane2.css index 604bca0..7b7c1db 100644 --- a/css/pane2.css +++ b/css/pane2.css @@ -32,7 +32,7 @@ #details { display: none; height: 100%; - width: 50%; + width: calc(50% - 4em); position: fixed; top: 0px; left: 50%; @@ -41,6 +41,7 @@ padding: 1em; overflow-y: scroll; background-color: #f8f0f0; + text-align: center; } #details a { @@ -118,15 +119,23 @@ #mapReturn { font-size: 2em; text-align: center; - position: absolute; +/* position: absolute;*/ /* bottom: 1em;*/ - margin-left: -5em; - left: 50%; +/* margin-left: -5em; + left: 50%;*/ width: 10em; + margin-bottom: 2em; } #detailsControls { padding: 0em 0em 5em 0em; + text-align: left; + width: 95%; +} + +#detailsControls select { + font-size: 2em; + margin-bottom: 1em; } #blackout { @@ -152,9 +161,14 @@ #voterBack { float: right; margin-right: 1em; + z-index: 100; } -@media screen and (orientation: portrait), (max-width: 600px) { +#voterSave { + z-index: 100; +} + +@media screen and (orientation: portrait), (max-width: 800px) { #map, #map.full { width: 100%; left: 0%; @@ -173,6 +187,11 @@ height: calc(100%); z-index: 0; } + + #voterSel { + width: 90% !important; + margin-left: -1em; + } } .loadingIndicator { @@ -198,7 +217,7 @@ border-collapse: collapse; display: block; } - +/* .category { position: relative; width: 100%; @@ -211,7 +230,6 @@ font-weight: bold; text-decoration: none; color: #FEE; - font-size: 2em; border-top: 2px solid white; box-sizing: border-box; } @@ -233,7 +251,7 @@ .category .toggle:hover { background-color: #800; -} +}*/ .category .list { position: relative; @@ -242,6 +260,8 @@ padding: 1em; } + + .category .list input { vertical-align: middle; } @@ -250,17 +270,35 @@ width: 100%; } +.category table { + margin-bottom: 3em; +} + .subcategory { margin-left: 1em; margin-right: 1em; - margin-top: 1em; + margin-top: 0.5em; margin-bottom:1em; color: #FEE; border-bottom: 1px dashed #FEE; } +.subcategory tr td:last-child { + height: 100%; +} + +.subcategory tr td strong { + font-size: 1.5em; +} + #canvassing-list .subcategory input { width: 100%; + height: 100%; + font-size: 1.1em; +} + +#canvassing-list label { + margin-top: 1em; } #canvassing-list .subcategory tr.new { @@ -292,9 +330,13 @@ } #voterSel, #voterSupportRange { - width: calc(100% - 4em); - height: 3em; + width: calc(100% - 6em); margin-bottom: 1em; + font-size: 3em; +} + +#voterSel:enabled { + border: 2px solid #FAA; } #canvassToolbar { @@ -306,7 +348,7 @@ text-align: center; } -#canvassToolbar button { +#canvassToolbar button:not(#geocoderAddrInputClose) { max-width: 15%; max-height: 15vw; background-color: #FEE; @@ -321,33 +363,57 @@ max-height: calc(15vw - 10px); } -#geocoderAddrInput, #geocoderAddrInputSubmit { +#geocoderAddrInput.error { + background-color: #A00; + transition: background-color 1000ms linear; +} + +#geocoderAddrInput, #geocoderAddrInputSubmit, #geocoderAddrInputClose { width: 0px; - height: 3em; + height: 2em; padding: 0px; margin: 0px; transition: width 1s; + font-size: 4em; + vertical-align: top; + display: none; + transition: background-color 1000ms linear; } #canvassToolbar.addrInput button, #canvassToolbar.addrInput button img { - width: 0px; - margin: 0px; - padding: 0px; + width: 0px !important; + margin: 0px !important; + padding: 0px !important; + display: none; } #canvassToolbar.addrInput #geocoderAddrInput { width: 50%; max-width: 80%; + display: inline-block; } #canvassToolbar.addrInput #geocoderAddrInputSubmit { min-width: 10%; + width: auto; + display: inline-block; +} + +#canvassToolbar.addrInput #geocoderAddrInputClose { + min-width: 1em; + width: auto; + display: inline-block; } + #detailsTable .notesCell { border-bottom: 6px solid black; } +#detailsTable input, #detailsTable select { + font-size: 2em; +} + #paginator { height: 2em; position: relative; @@ -361,3 +427,95 @@ #paginator a { padding: 0em 1em; } + +.leaflet-voterCount { + font-weight: 900; + background: transparent !important; + margin-top: -60px; + border: transparent !important; + box-shadow: none !important; + margin-left: 10px !important; + color: #0C0 !important; + text-shadow: -1px 1px 0 #000, + 1px 1px 0 #000, + 1px -1px 0 #000, + -1px -1px 0 #000; + font-size: 32px; +} + +.leaflet-voterCount:before { + display: none; + border: none; +} + +#voterSaveStatus { + font-weight: 900; + text-align: center; +} + +#voterSaveStatus { + display: inline; +} + +#voterSaveStatus.unsaved::before { + content: 'Unsaved Changes'; + color: red; + width: 75%; + display: block; + left: 10%; + position: absolute; + margin-top: -2em; + text-shadow: -1px 1px 0 #000, + 1px 1px 0 #000, + 1px -1px 0 #000, + -1px -1px 0 #000; + z-index: 50; +} + +#voterSaveStatus.error::before { + content: 'An error occurred.\aPlease try again or contact support.'; + font-size: 0.8em; + color: red; + width: 75%; + display: block; + left: 10%; + position: absolute; + margin-top: -2em; + white-space: pre; + text-shadow: -1px 1px 0 #000, + 1px 1px 0 #000, + 1px -1px 0 #000, + -1px -1px 0 #000; + z-index: 50; +} + + +#voterSaveStatus.saved::before { + content: 'Saved.'; + color: green; + width: 75%; + display: block; + left: 10%; + position: absolute; + margin-top: -2em; + text-shadow: -1px 1px 0 #000, + 1px 1px 0 #000, + 1px -1px 0 #000, + -1px -1px 0 #000; + z-index: 50; +} + + + +/*.leaflet-marker { + width: 96px; +}*/ + +#geocoderLastBtn:disabled img { + opacity: 0.3; +} + +.promptNotes { + font-style: italic; +} + diff --git a/data/ElectionDB.sqlite3 b/data/ElectionDB.sqlite3 new file mode 100755 index 0000000..4af9dc7 Binary files /dev/null and b/data/ElectionDB.sqlite3 differ diff --git a/images/Home.png b/images/Home.png new file mode 100644 index 0000000..426e435 Binary files /dev/null and b/images/Home.png differ diff --git a/images/marker-icon-gray.png b/images/marker-icon-gray.png new file mode 100644 index 0000000..f392f11 Binary files /dev/null and b/images/marker-icon-gray.png differ diff --git a/index.php b/index.php index 82744c5..2555307 100644 --- a/index.php +++ b/index.php @@ -17,8 +17,10 @@ * If not, see . */ --> + + @@ -50,6 +52,6 @@

Phonebank - Make calls to voters within a designated turf

Settings - Configure CCCP

- + diff --git a/js/canvassing.js b/js/canvassing.js index c5ec03f..2b1cd35 100644 --- a/js/canvassing.js +++ b/js/canvassing.js @@ -17,6 +17,7 @@ // Enable drawing functions for cutting turf // (Using LeafletJS leaflet-draw plugin function enableDraw() { + debug("enableDraw()"); if( document.getElementsByClassName("leaflet-draw").length > 0) { return; } @@ -91,10 +92,12 @@ function enableDraw() { console.log("noop"); }); }); + debug("End enableDraw"); } // Disable drawing of turf function disableDraw(drawControl) { + debug("disableDraw("+drawControl+")"); map.removeControl(drawControl); var layerKeys = Object.keys(map._layers); for(var i = 0; i < layerKeys.length; i++) { @@ -102,12 +105,17 @@ function disableDraw(drawControl) { map.removeLayer(map._layers[layerKeys[i]]); } } + debug("End disableDraw"); } // Show/hide a particular turf var turfLayer = null; -function toggleTurf(turf, event) { - if( turfLayer != null ) { +function toggleTurf(turf, value, event) { + debug("toggleTurf(..., ..., ...)"); + debug(turf); + debug(value); + debug(event); + if( turfLayer != null && value != true) { map.removeLayer(turfLayer); turfLayer = null; return; @@ -118,8 +126,11 @@ function toggleTurf(turf, event) { } var coordinates = []; - var turfjs = JSON.parse(turf.json); - for(var i = 0; i < turfjs.length; i++) { + if(turf.json == null) { + return; + } + var geojs = JSON.parse(turf.json); +/* for(var i = 0; i < turfjs.length; i++) { coordinates.push([turfjs[i]["lng"], turfjs[i]["lat"]]); } var geometry = Object(); @@ -133,12 +144,14 @@ function toggleTurf(turf, event) { featObj.properties.NAME = turf.name; featObj.properties.OBJECTID = 2; - var geojs = geoJsonBuilder(JSON.stringify(featObj)); + var geojs = geoJsonBuilder(JSON.stringify(featObj));*/ turfLayer = L.geoJSON(geojs).addTo(map); + debug("End toggleTurf"); } // Load turfs from the database function loadTurfs(research = false) { + debug("loadTurfs("+research+")"); setLoading(1); fetch("api.php?get=canvasses").then(data => data.json()) .then(turfs => { @@ -240,7 +253,7 @@ function loadTurfs(research = false) { var show = document.createElement("input"); show.setAttribute("type", "button"); show.setAttribute("value", "Show on map"); - show.onclick = toggleTurf.bind(null, turfs[i], null); + show.onclick = toggleTurf.bind(null, turfs[i], true, null); header.appendChild(show); turfList.appendChild(header); } @@ -262,16 +275,21 @@ function loadTurfs(research = false) { } } setLoading(-1); + debug("End Async loadTurfs"); }); + debug("End Sync loadTurfs"); } // Intermediate callback to handle UI portion of saving a turf // Calls saveTurfToDB to actually persist it function saveTurf(drawControl) { + debug("saveTurf("+drawControl+")"); var layerKeys = Object.keys(map._layers); for(var i = 0; i < layerKeys.length; i++) { if( typeof map._layers[layerKeys[i]].type != 'undefined' ) { - var turfPoints = JSON.stringify(map._layers[layerKeys[i]]._latlngs[0]); + var turfjson = JSON.stringify(map._layers[layerKeys[i]].toGeoJSON()); +console.log(turfjson); +console.log("CHECK"); var blackoutElem = document.createElement("div"); var nameElem = document.createElement("div"); @@ -291,19 +309,23 @@ function saveTurf(drawControl) { nameButton.setAttribute("type", "button"); nameButton.setAttribute("id", "turfNameButton"); nameButton.setAttribute("value", "Save"); - nameButton.onclick = saveTurfToDB.bind(null, turfPoints); + nameButton.onclick = saveTurfToDB.bind(null, turfjson); nameElem.appendChild(nameButton); document.getElementById("map").appendChild(blackoutElem); document.getElementById("map").appendChild(nameElem); disableDraw(drawControl); + debug("End saveTurf (Early)"); return; } } + debug("End saveTurf"); } // Write the turf data to the database function saveTurfToDB(turfPoints) { + debug("saveTurfToDB(...)"); + debug(turfPoints); var name = document.getElementById("turfNameInput").value; var options = { @@ -318,12 +340,13 @@ function saveTurfToDB(turfPoints) { document.getElementById("map").removeChild(document.getElementById("blackout")); document.getElementById("map").removeChild(document.getElementById("turfNameEntry")); - + debug("End saveTurfToDB"); // loadTurfs(); } // Start a new canvass for a given turf function createCanvass(turfId) { + debug("createCanvass("+turfId+")"); var name = document.getElementById("new-canvass-"+turfId).value; if( name == "") { name = document.getElementById("new-canvass-"+turfId).getAttribute("placeholder"); @@ -336,49 +359,62 @@ function createCanvass(turfId) { fetch("api.php?set=canvass", options).then(data => data.json()) .then(resp => { loadTurfs(true); + debug("End Async createCanvass"); }); + debug("End Sync createCanvass"); } // Display a canvasses's turf function viewCanvass(canvass) { + debug("viewCanvass(...)"); + debug(canvass); setLoading(1); + currentCanvass = canvass; fetch("api.php?get=canvasses&id="+canvass.turfId).then(data => data.json()) .then(turfs => { for(var i = 0; i < turfs.length; i++) { - toggleTurf(turfs[i], null); + toggleTurf(turfs[i], null, null); // document.getElementById("turf-"+turfs[i].id).checked = true; } setVoterString(canvass.voterString); setLoading(-1); + debug("End Async viewCanvass"); }); + debug("End Sync viewCanvass"); } // Don't feel like figuring out how to bind additional params to updateLocation // so let's just use a global... +// (This is also now used for getting canvass scripts!) var currentCanvass = null; var currentMarker = null; // Start walking a canvass function startCanvass(canvass, turf) { + debug("startCanvass(..., ...)"); + debug(canvass); + debug(turf); setLoading(1); - map.on("moveend", loadVoters.bind(null, false, window.location.pathname.indexOf("research.php") == -1)); + map.on("moveend", loadVoters.bind(null, false, false)); loadTurfs(); var json = turf.json; - var turfPoints = JSON.parse(json); + var turfPoints = JSON.parse(json).geometry.coordinates[0]; var latTot = 0; var lonTot = 0; var i = 0; for(i = 0; i < turfPoints.length; i++) { - latTot += turfPoints[i].lat; - lonTot += turfPoints[i].lng; + latTot += turfPoints[i][1]; + lonTot += turfPoints[i][0]; } map.setZoom(19); map.setView([latTot/i, lonTot/i]); enableCanvassControls(); currentCanvass = canvass; +// turfLayer = turf; + setTimeout(toggleTurf.bind(null, turf, true), 1000); viewCanvass(canvass); var options = { @@ -389,16 +425,21 @@ function startCanvass(canvass, turf) { } fetch("api.php?set=canvass", options).then(data => data.json()) .then(resp => { - loadVoters(false, window.location.pathname.indexOf("research.php") == -1); + loadVoters(false, false); + debug("End Async startCanvass"); }); toggleControls(); setLoading(-1); + debug("End Sync startCanvass"); } // Delete a canvass function deleteCanvass(canvass, turf) { + debug("deleteCanvass(..., ...)"); + debug(canvass); + debug(turf); setLoading(1); var options = { @@ -411,12 +452,16 @@ function deleteCanvass(canvass, turf) { console.log(resp); loadTurfs(true); setLoading(-1); + debug("End Async deleteCanvass"); }); + debug("End Sync deleteCanvass"); } // function endCanvass(canvass) { + debug("endCanvass(...)"); + debug(canvass); setLoading(1); var options = { @@ -428,15 +473,19 @@ function endCanvass(canvass) { fetch("api.php?set=canvass", options).then(data => data.json()) .then(resp => { console.log(resp); + debug("End Async endCanvass"); }); loadTurfs(); setLoading(-1); + debug("End Sync endCanvass"); } // function updateLocation(position) { //alert("CHECK2"); + debug("updateLocation(...)"); + debug(position); map.setView([position.coords.latitude, position.coords.longitude ]) if(currentMarker != null) { map.removeLayer(currentMarker); @@ -450,24 +499,32 @@ function updateLocation(position) { currentMarker = new L.marker([position.coords.latitude, position.coords.longitude], {icon: markerIcon, persist: true}).addTo(map); setTimeout( function() { viewCanvass(currentCanvass); }, 1000); // setTimeout( function() { navigator.geolocation.getCurrentPosition(updateLocation); }, 60000); + debug("End updateLocation"); } // function locationFailure(arg) { + debug("locationFailure("+arg+")"); toastMessage("Unable to update location:
"+arg.message) navigator.geolocation.clearWatch(geoWatcher); this.classList.remove("watching"); geoWatcher = null; + debug("End locationFailure"); } function toggleControls() { + debug("toggleControls()"); enableCanvassControls(); + debug("End toggleControls()"); } // var geoWatcher = null; +var addrErr = null; function enableCanvassControls() { + debug("enableCanvassControls()"); if( document.getElementById("canvassToolbar") != null) { + debug("End enableCanvassControls (Early)"); return; } @@ -483,6 +540,11 @@ function enableCanvassControls() { position.coords.latitude = e.geocode.center.lat; position.coords.longitude = e.geocode.center.lng; updateLocation(position); + + if(this.manual) { + clearTimeout(addrErr); + document.getElementById("canvassToolbar").classList.remove("addrInput"); + } }) .addTo(map); var geoctl = document.getElementsByClassName("leaflet-control-geocoder")[0]; @@ -496,7 +558,7 @@ function enableCanvassControls() { gpsBtn.onclick = function() { // navigator.geolocation.getCurrentPosition(updateLocation, locationFailure); if(geoWatcher == null) { - geoWatcher = navigator.geolocation.watchPosition(updateLocation, locationFailure); + geoWatcher = navigator.geolocation.watchPosition(updateLocation, locationFailure, {maximumAge: 30000}); this.classList.add("watching"); } else { navigator.geolocation.clearWatch(geoWatcher); @@ -531,6 +593,15 @@ function enableCanvassControls() { addrInput.addEventListener("keypress", function(event) { if (event.key === "Enter") { document.getElementById("geocoderAddrInputSubmit").click(); +/* var err = function() { + alert("No results found"); + document.getElementById("geocoderAddrInput").classList.add("error"); + var unerr = function() { + document.getElementById("geocoderAddrInput").classList.remove("error"); + } + setTimeout(unerr, 100); + } + addrErr = setTimeout(err, 1000);*/ } }); toolbar.appendChild(addrInput); @@ -540,16 +611,40 @@ function enableCanvassControls() { addrInputSubmit.value = "Search"; addrInputSubmit.setAttribute("id", "geocoderAddrInputSubmit"); addrInputSubmit.onclick = function(cdr) { + // Set an error on a timer + // If the search hits, it will clear the timer + // (Can't find a better way to do this...I tried!) + var err = function() { + alert("No results found"); +/* document.getElementById("geocoderAddrInput").classList.add("error"); + var unerr = function() { + document.getElementById("geocoderAddrInput").classList.remove("error"); + } + setTimeout(unerr, 100);*/ + } + addrErr = setTimeout(err, 1000); + cdr.setQuery(document.getElementById("geocoderAddrInput").value); cdr._geocode(); - document.getElementById("canvassToolbar").classList.remove("addrInput"); + cdr.manual = true; +// document.getElementById("canvassToolbar").classList.remove("addrInput"); }.bind(null, coder); toolbar.appendChild(addrInputSubmit); + var addrInputClose = document.createElement("button"); +// addrInputSubmit.setAttribute("type", "button"); + addrInputClose.innerText = "X"; + addrInputClose.setAttribute("id", "geocoderAddrInputClose"); + addrInputClose.onclick = function() { + document.getElementById("canvassToolbar").classList.remove("addrInput"); + } + toolbar.appendChild(addrInputClose); + + var addBtn = document.createElement("button"); // Without the setTimeout here it triggers the map click *after* the button click // ...which drops the pin immediately, hidden behind the button - addBtn.onclick = function() { setTimeout(manualAdd, 10); }; + addBtn.onclick = manualDetails.bind(null, null); //function() { setTimeout(manualAdd, 10); }; var addImg = document.createElement("img"); addImg.src = "images/ManualAdd.png"; addBtn.appendChild(addImg); @@ -558,7 +653,7 @@ function enableCanvassControls() { var refreshBtn = document.createElement("button"); refreshBtn.onclick = function() { - loadVoters(true, window.location.pathname.indexOf("research.php") == -1); + loadVoters(true, false); } var refreshImg = document.createElement("img"); refreshImg.src = "images/RefreshVoters.png"; @@ -571,6 +666,7 @@ function enableCanvassControls() { document.getElementById("details").style.display = "block"; document.getElementById("map").style.display = "none"; } + lastBtn.setAttribute("disabled", "true"); var lastImg = document.createElement("img"); lastImg.src = "images/LastVoter.png"; lastBtn.appendChild(lastImg); @@ -589,19 +685,24 @@ function enableCanvassControls() { } menuBtn.setAttribute("id", "geocoderMenuBtn"); var menuImg = document.createElement("img"); - menuImg.src = "images/Menu.png"; + menuImg.src = "images/Home.png"; menuBtn.appendChild(menuImg); toolbar.appendChild(menuBtn); map.invalidateSize(); document.getElementById("map").appendChild(toolbar); + debug("End enableCanvassControls"); } function manualAdd() { + debug("manualAdd()"); map.on('click', manualAddClick); + debug("End manualAdd()"); } function manualAddClick(e) { + debug("manualAddClick(...)"); + debug(e); var markerIcon = L.icon({ iconUrl: "images/marker-icon-white.png", iconSize: [24, 36], @@ -617,9 +718,12 @@ function manualAddClick(e) { marker.on('click', manualDetails.bind(null, marker)); map.addLayer(marker); map.off('click'); + debug("End manualAddClick"); } function manualDetails(marker) { + debug("manualDetails(...)"); + debug(marker); var results = Object(); results.firstName = "unknown"; results.middleName = ""; @@ -633,16 +737,23 @@ function manualDetails(marker) { results.city = null; results.state = "RI"; results.zip = null; - results.lat = marker._latlng.lat; - results.lon = marker._latlng.lng; + if(marker != null && marker._latlng != null) { + results.lat = marker._latlng.lat; + results.lon = marker._latlng.lng; + } showVoterInfo(results, null, null, true); - updateMarker(marker); - document.getElementById("voterDetailsparty").onchange = updateMarker.bind(null, marker); + if(marker != null) { + updateMarker(marker); + document.getElementById("voterDetailsparty").onchange = updateMarker.bind(null, marker); + } + debug("End manualDetails"); } function updateMarker(marker) { + debug("updateMarker(...)"); + debug(marker); var party = document.getElementById("voterDetailsparty").value; var url = "images/marker-icon-"; if(party == "Democrat") { @@ -660,14 +771,18 @@ function updateMarker(marker) { iconAnchor: [12,36] }); marker.setIcon(markerIcon); + debug("End updateMarker"); } function showCanvassStats(id, count, page=0) { + debug("showCanvassStats("+id+", "+count+", "+page+")"); if( typeof page == 'object' ) { page = 0; } fetch("api.php?get=canvassResults&id="+id+"&page="+page+"&pageSize=50").then(data => data.json()) - .then(resp => { + .then(output => { + + resp = output.dataset; // Clear any existing voter details from the panel var detailsPane = document.getElementById("details"); detailsPane.innerHTML = '
\ @@ -694,9 +809,9 @@ function showCanvassStats(id, count, page=0) { var cell = document.createElement("th"); cell.innerHTML = "Contact Method"; row.appendChild(cell); - var cell = document.createElement("th"); +/* var cell = document.createElement("th"); cell.innerHTML = "Est. Support"; - row.appendChild(cell); + row.appendChild(cell);*/ var cell = document.createElement("th"); cell.innerHTML = "Corrections"; row.appendChild(cell); @@ -715,9 +830,9 @@ function showCanvassStats(id, count, page=0) { var cell = document.createElement("th"); cell.innerHTML = "Birthdate"; row.appendChild(cell); - var cell = document.createElement("th"); +/* var cell = document.createElement("th"); cell.innerHTML = "Sex"; - row.appendChild(cell); + row.appendChild(cell);*/ var cell = document.createElement("th"); cell.innerHTML = "Party"; row.appendChild(cell); @@ -741,13 +856,17 @@ function showCanvassStats(id, count, page=0) { row.appendChild(cell); table.appendChild(row); + var script = output.script; + var row = document.createElement("tr"); + for(var i = 0; i < resp.length; i++) { +console.log("CHECKING: "+i); var row = document.createElement("tr"); var cell = document.createElement("td"); - cell.innerHTML = new Date(parseInt(resp[i].timestamp)).toLocaleString(); + cell.innerHTML = resp[i].timestamp; row.appendChild(cell); var cell = document.createElement("td"); - cell.innerHTML = resp[i].contactUser; + cell.innerHTML = resp[i].userId; row.appendChild(cell); var cell = document.createElement("td"); cell.innerHTML = resp[i].contactStatus; @@ -755,9 +874,9 @@ function showCanvassStats(id, count, page=0) { var cell = document.createElement("td"); cell.innerHTML = resp[i].contactMethod; row.appendChild(cell); - var cell = document.createElement("td"); +/* var cell = document.createElement("td"); cell.innerHTML = resp[i].estSupportPct; - row.appendChild(cell); + row.appendChild(cell);*/ var cell = document.createElement("td"); cell.innerHTML = resp[i].correctionsNeeded; row.appendChild(cell); @@ -774,11 +893,11 @@ function showCanvassStats(id, count, page=0) { cell.innerHTML = resp[i].name; row.appendChild(cell); var cell = document.createElement("td"); - cell.innerHTML = resp[i].birthdate; + cell.innerHTML = resp[i].birthyear; row.appendChild(cell); - var cell = document.createElement("td"); +/* var cell = document.createElement("td"); cell.innerHTML = resp[i].sex; - row.appendChild(cell); + row.appendChild(cell);*/ var cell = document.createElement("td"); cell.innerHTML = resp[i].party; row.appendChild(cell); @@ -802,6 +921,7 @@ function showCanvassStats(id, count, page=0) { row.appendChild(cell); table.appendChild(row); } +console.log("AT PAGINATOR"); var paginator = document.createElement("div"); paginator.setAttribute("id", "paginator"); @@ -838,7 +958,9 @@ function showCanvassStats(id, count, page=0) { paginator.appendChild(downLink); document.getElementById("detailsControls").prepend(paginator); + debug("End Async showCanvassStats"); }); + debug("End Sync showCanvassStats"); } diff --git a/js/common.js b/js/common.js index 629f924..2aecf62 100644 --- a/js/common.js +++ b/js/common.js @@ -64,6 +64,14 @@ TODO: Comment code more, especially: * If not, see . */ +var _CCCP_DEBUG = true; +function debug(msg) { + if(_CCCP_DEBUG) { +// debugger; + console.log(msg); + } +} + // Stores data associated to precincts, blocks, or block groups for display var displayAreas = []; @@ -74,7 +82,16 @@ var colors = ["#FF0000", "#00FF00", "#0000FF", "#FFFF00", "#00FFFF", "#FF00FF", // INITIALIZATION /////////////////////////////////////////////////////////// +var initTimeout = 10; function init() { + debug("init()"); + + if( typeof L == "undefined" && initTimeout > 0) { + setTimeout(init, 500); + initTimeout--; + return; + } + // Initialize the map -- LeafletJS boilerplate map = L.map('map').setView([41.5, -71.5 ], 10); L.tileLayer('https://{s}.tile.osm.org/{z}/{x}/{y}.png' , { maxZoom: 25}).addTo(map); @@ -104,6 +121,7 @@ function init() { // Show/Hide Loading Indicator var loadingElems = 0; function setLoading(items) { + debug("setLoading("+items+")"); loadingElems += items; var indicators = document.getElementsByClassName("loadingIndicator"); if(loadingElems == 0) { @@ -124,27 +142,40 @@ function setLoading(items) { // Show/hide one of the major categories function toggleList(e) { + debug("toggleList(...)"); + debug(e); + + if(e.target.class == "toggle") + var target = e.target; + else + var target = e.target.parentElement.getElementsByClassName("toggle")[0]; + var list = e.target.parentElement.getElementsByClassName("list")[0]; if(list.style.display != "block") { list.style.display = "block"; - e.target.innerHTML = "-"; + target.innerHTML = "-"; } else { list.style.display = "none"; - e.target.innerHTML = "+"; + target.innerHTML = "+"; } } // Return to the map from stats or voter details function restoreMap() { + debug("restoreMap()"); if(document.getElementById("paginator") != null) { document.getElementById("controls").style.display = "block"; } document.getElementById("details").style.display = "none"; document.getElementById("map").style.display = "block"; + map.invalidateSize(); + loadVoters(true, false); } // Utility function to complete geojsons taken from the database function geoJsonBuilder(jsonStr) { + debug("geoJsonBuilder(...)"); + debug(jsonStr); var geojs = Object(); geojs.type = "FeatureCollection"; geojs.name = "demoArea_"; @@ -175,6 +206,8 @@ function geoJsonBuilder(jsonStr) { // Show a "toast" style notification function toastMessage(message) { + debug("toastMessage(...)"); + debug(message); var toastDiv = document.createElement("div"); toastDiv.innerHTML = message; toastDiv.setAttribute("id", "toastDiv"); @@ -190,6 +223,7 @@ function toastMessage(message) { // Toggle map display function toggleControls() { + debug("toggleControls()"); var controlElem = document.getElementById("controls"); var mapElem = document.getElementById("map"); if( controlElem.getAttribute("class") == "min") { diff --git a/js/demographics.js b/js/demographics.js index 01a9447..fe02f14 100644 --- a/js/demographics.js +++ b/js/demographics.js @@ -16,6 +16,7 @@ // Load the demographics categories from the database function loadDemographics() { + debug("loadDemographics()"); setLoading(1); // Clear any existing displays @@ -81,12 +82,16 @@ function loadDemographics() { // Re-enable the type select when done document.getElementById("demoType").removeAttribute("disabled"); setLoading(-1); + debug("End Async loadDemographics"); }); + debug("End Sync loadDemographics"); } // TODO: comment; clear or re-check when changing modes // Enable/disable display of demographic stats function toggleDemo(demoId, colorId, event) { + debug("toggleDemo("+demoId+", "+colorId+", ...)"); + debug(event); setLoading(1); fetch("api.php?get=demos&id="+demoId).then(data => data.json()) .then(demos => { @@ -183,11 +188,14 @@ function toggleDemo(demoId, colorId, event) { } } setLoading(-1); + debug("End Async toggleDemo"); }); + debug("End Sync toggleDemo"); } // Update the transparency of demographics areas (onchange event from the slider) function updateDemoTransparency() { + debug("updateDemoTransparency()"); // Match the selected type to the displayAreas list for that type for(var das = 0; das < displayAreas.length; das++) { if(displayAreas[das][0].type == document.getElementById("demoType").value) { @@ -219,4 +227,5 @@ function updateDemoTransparency() { displayAreas[das][da].mapElem.setStyle({fillOpacity: opacity, strokeOpacity: opacity}); } } + debug("End updateDemoTransparency"); } diff --git a/js/districts.js b/js/districts.js index 3b5ed92..f71c28b 100644 --- a/js/districts.js +++ b/js/districts.js @@ -16,6 +16,7 @@ // Populate the list of district types in the menu function loadDistrictTypes() { + debug("loadDistrictTypes()"); setLoading(1); fetch("api.php?get=district").then(data => data.json()) .then(districtTypes => { @@ -29,11 +30,14 @@ function loadDistrictTypes() { } loadDistricts(); setLoading(-1); + debug("End Async loadDistrictTypes"); }); + debug("End Sync loadDistrictTypes"); } // Populate the list of specific districts function loadDistricts() { + debug("loadDistricts()"); setLoading(1); var type = document.getElementById("districtTypeSel").value; fetch("api.php?get=district&type="+type).then(data => data.json()) @@ -47,12 +51,15 @@ function loadDistricts() { selector.appendChild(option); } setLoading(-1); + debug("End Async loadDistricts"); }); + debug("End Sync loadDistricts"); } // Enable/disable the display of a particular district displayDistricts = Array(); function showDistrict(targetState) { + debug("showDistricts("+targetState+")"); setLoading(1); var districtId = document.getElementById("districtSel").value; var type = document.getElementById("districtTypeSel").value; @@ -98,20 +105,24 @@ function showDistrict(targetState) { document.getElementById("districtsTable").appendChild(row); } setLoading(-1); + debug("End Async showDistricts"); }); + debug("End Sync showDistricts"); } function hideDistrict(districtId) { -console.log(districtId); + debug("hideDistrict("+districtId+")"); if(displayDistricts[districtId] != null) { map.removeLayer(displayDistricts[districtId].mapElem); displayDistricts[districtId].mapElem = null; this.parentElement.removeChild(this); } + debug("End hideDistrict"); } // Update the transparency of district areas (onchange event from the slider) function updateDistrictTransparency() { + debug("updateDistrictTransparency()"); var districtKeys = Object.keys(displayDistricts); for(var i = 0; i < districtKeys.length; i++) { if(displayDistricts[districtKeys[i]].mapElem != null) { @@ -119,5 +130,6 @@ function updateDistrictTransparency() { displayDistricts[districtKeys[i]].mapElem.setStyle({opacity: opacity, fillOpacity: opacity, weight: 2, strokeOpacity: 1}); } } + debug("End updateDistrictTransparency"); } diff --git a/js/precincts.js b/js/precincts.js index bb05d37..3cd2647 100644 --- a/js/precincts.js +++ b/js/precincts.js @@ -17,6 +17,7 @@ // Populate the list of precincts in the menu function loadPrecincts() { + debug("loadPrecincts()"); fetch("api.php?get=precincts").then(data => data.json()) .then(precinctList => { var cities = Object.keys(precinctList); @@ -60,11 +61,15 @@ function loadPrecincts() { document.getElementById("precinct-list").appendChild(cityClr); document.getElementById("precinct-list").appendChild(cityList); } + debug("End Async loadPrecincts"); }); + debug("End Sync loadPrecincts"); } // Enable/disable a precinct displayed on the map function togglePrecincts(pids, colorId=null, targetState = null) { + debug("togglePrecincts(..., "+colorId+", "+targetState+")"); + debug(pids); setLoading(1); console.log("togglePrecincts"); if(colorId != null) { @@ -114,10 +119,12 @@ console.log(geojs); togglePrecincts.bind(null, pids, colorId, !targetState); } setLoading(-1); + debug("End togglePrecincts"); } // Update the transparency of precinct areas (onchange event from the slider) function updatePrecinctTransparency() { + debug("updatePrecinctTransparency()"); // Find the displayAreas list for precincts for(var das = 0; das < displayAreas.length; das++) { if(displayAreas[das][0].type == "precinct") { @@ -131,4 +138,5 @@ function updatePrecinctTransparency() { displayAreas[das][i].mapElem.setStyle({opacity: opacity, fillOpacity: opacity}); } } + debug("End updatePrecinctTransparency"); } diff --git a/js/voters.js b/js/voters.js index 3ba2285..cf503f2 100644 --- a/js/voters.js +++ b/js/voters.js @@ -17,15 +17,21 @@ // Load and display the selected voters var lastLoc = {}; function loadVoters(force = false, pending) { + debug("loadVoters("+force+", "+pending+")"); setLoading(1); + // Sometimes on mobile you can accidentally zoom the page instead of the map + // Then all the buttons get lost and it's a mess to restore + // But this call with trigger with some scrolling so that should help! + document.body.style.zoom = "100%"; + mapHeight = Math.abs(map.getBounds()._southWest.lat - map.getBounds()._northEast.lat); + mapWidth = Math.abs(map.getBounds()._southWest.lng - map.getBounds()._northEast.lng); if(lastLoc.lat > map.getBounds()._southWest.lat && lastLoc.lat < map.getBounds()._northEast.lat && lastLoc.lng > map.getBounds()._southWest.lng && lastLoc.lng < map.getBounds()._northEast.lng && force == false) { - console.log("SKIP REFRESH"); setLoading(-1); return; } @@ -34,7 +40,8 @@ function loadVoters(force = false, pending) { // Clear any existing pins on the map var layerKeys = Object.keys(map._layers); for(var i = 0; i < layerKeys.length; i++) { - if(typeof map._layers[layerKeys[i]].options.icon != "undefined" && + if(typeof map._layers[layerKeys[i]] != "undefined" && + typeof map._layers[layerKeys[i]].options.icon != "undefined" && map._layers[layerKeys[i]].options.persist != true) { map.removeLayer(map._layers[layerKeys[i]]); } @@ -42,10 +49,10 @@ function loadVoters(force = false, pending) { // Build the request parameters var latlon = ""; - var minLat = map.getBounds()._southWest.lat; - var minLon = map.getBounds()._southWest.lng; - var maxLat = map.getBounds()._northEast.lat; - var maxLon = map.getBounds()._northEast.lng; + var minLat = map.getBounds()._southWest.lat - mapHeight/4; + var minLon = map.getBounds()._southWest.lng - mapWidth/4; + var maxLat = map.getBounds()._northEast.lat + mapHeight/4; + var maxLon = map.getBounds()._northEast.lng + mapWidth/4; if(turfLayer != null) { if(minLat < turfLayer.getBounds()._southWest.lat) { minLat = turfLayer.getBounds()._southWest.lat; @@ -71,11 +78,17 @@ function loadVoters(force = false, pending) { if(pending) { pendingStr = "&pending"; } + + var turfStr = ""; + if(currentCanvass != null) { + turfStr = "&turfId="+currentCanvass.turfId; + } + // Send the request and drop the map markers var options = { method: "POST", headers: {"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"}, - body: getVoterString()+latlon+pendingStr + body: getVoterString()+latlon+pendingStr+turfStr } fetch("api.php?get=voterLocs", options).then(data => data.json()) .then(locObj => { @@ -105,23 +118,58 @@ function loadVoters(force = false, pending) { if(locList[i].icon) { iconImg = locList[i].icon; } + if(locList[i].contacted != "0") { + iconImg = "marker-icon-gray.png"; + } var markerIcon = L.icon({ iconUrl: "images/"+iconImg, - iconSize: [24, 36], - iconAnchor: [12,36], + iconSize: [48, 72], + iconAnchor: [24,72], + className: 'leaflet-marker' }); var markerTitle = locList[i].addressLine1 + " ("+locList[i].count+" voters)"; var newMarker = new L.marker([locList[i].latitude, locList[i].longitude], {icon: markerIcon, title: markerTitle}).addTo(map); + if( locList[i].count > 1) { + newMarker.bindTooltip(locList[i].count+"", + { + permanent: true, + direction: 'right', + className: 'leaflet-voterCount' + } + ); + } + /*var markerText = new L.Marker([locList[i].latitude, locList[i].longitude], { + icon: new L.DivIcon({ + className: 'my-div-icon', + html: '---'+locList[i].count+'-------' + }) +});*/ newMarker.on('click', voterList.bind(null, locList[i].latitude, locList[i].longitude)); } setLoading(-1); + debug("End Async loadVoters"); }); + + // Ensure any turf is displaying + function showTurf() { + if(turfLayer != null) { + toggleTurf(turfLayer, true, null); + } + } + + // Race condition mostly seen on mobile means this doesn't always have the turf right away +// showTurf(); + setTimeout(showTurf, 1000); + debug("End Sync loadVoters"); } + // Prepare a list of voters for the details screen function voterList(lat, lon, event) { + debug("voterList("+lat+", "+lon+", ...)"); + debug(event); setLoading(1); var options = { method: "POST", @@ -152,12 +200,22 @@ function voterList(lat, lon, event) { var id = document.getElementById("voterSel").value; voterDetails(id); }; + + if( details.length == 1 ) { + select.disabled = true; + } else { + select.disabled = null; + } + debug("End Async voterList"); }); setLoading(-1); + debug("End Sync voterList"); } // Show details of a voter when clicking their map pin function voterDetails(id, event) { + debug("voterDetails("+id+", ...)"); + debug(event); setLoading(1); showVoterInfo(null, null, null, false); fetch("api.php?get=voterDetails&id="+id).then(data => data.json()) @@ -171,11 +229,22 @@ function voterDetails(id, event) { custom = details.custom; showVoterInfo(voter, results, custom, false); setLoading(-1); + debug("End Async voterDetails"); }); + debug("End Sync voterDetails"); } // function showVoterInfo(details, results, custom, editable) { + debug("showVoterInfo(...,...,...,...)"); + debug(details); + debug(results); + debug(custom); + debug(editable); + + if(document.getElementById("geocoderLastBtn") != null) { + document.getElementById("geocoderLastBtn").removeAttribute("disabled"); + } document.getElementById("details").style.display = "block"; document.getElementById("map").style.display = "none"; @@ -274,13 +343,13 @@ function showVoterInfo(details, results, custom, editable) { voterId.value = details.id; controls.appendChild(voterId); - var value = results.estSupportPct; +/* var value = results.estSupportPct; if(value == undefined) { value = 0; } var supportLabel = document.createElement("label"); supportLabel.setAttribute("for", "voterSupportRange"); - supportLabel.innerHTML = "Estimated Support Level:"; + supportLabel.innerHTML = "Estimated Support Level:";*/ /* var supportRange = document.createElement("input"); supportRange.setAttribute("id", "voterSupportRange"); supportRange.setAttribute("type", "range"); @@ -299,15 +368,16 @@ function showVoterInfo(details, results, custom, editable) { controls.appendChild(supportLabel); controls.appendChild(supportRange); controls.appendChild(supportText);*/ - var supportList = document.createElement("select"); + +/* var supportList = document.createElement("select"); supportList.setAttribute("id", "voterSupportRange"); var supportOpt = document.createElement("option"); - supportOpt.innerHTML = "Extremely Opposed"; + supportOpt.innerHTML = "Definitely Opposed"; supportOpt.value = -100; supportList.appendChild(supportOpt); supportOpt = document.createElement("option"); - supportOpt.innerHTML = "Very Opposed"; + supportOpt.innerHTML = "Somewhat Opposed"; supportOpt.value = -60; supportList.appendChild(supportOpt); supportOpt = document.createElement("option"); @@ -315,7 +385,7 @@ function showVoterInfo(details, results, custom, editable) { supportOpt.value = -30; supportList.appendChild(supportOpt); supportOpt = document.createElement("option"); - supportOpt.innerHTML = "Neutral"; + supportOpt.innerHTML = "Unconvinced"; supportOpt.value = 0; supportList.appendChild(supportOpt); supportOpt = document.createElement("option"); @@ -323,11 +393,11 @@ function showVoterInfo(details, results, custom, editable) { supportOpt.value = 30; supportList.appendChild(supportOpt); supportOpt = document.createElement("option"); - supportOpt.innerHTML = "Very Supportive"; + supportOpt.innerHTML = "Somewhat Support"; supportOpt.value = 60; supportList.appendChild(supportOpt); supportOpt = document.createElement("option"); - supportOpt.innerHTML = "Extremely Supportive"; + supportOpt.innerHTML = "Definite Support"; supportOpt.value = 100; supportList.appendChild(supportOpt); supportList.onchange = function() { @@ -340,6 +410,73 @@ function showVoterInfo(details, results, custom, editable) { if(supportList.children[j].value <= value) { supportList.value = supportList.children[j].value; } + }*/ + + if( results.json != null ) { + var responses = JSON.parse(results.json); + } else { + var responses = []; + } + + if( currentCanvass != null && currentCanvass.script != null ) { + var script = JSON.parse(currentCanvass.script); + var introLabel = document.createElement("label"); + introLabel.setAttribute("for", "intro"); + introLabel.innerHTML = "Intro:"; + var introElem = document.createElement("p"); + introElem.setAttribute("id", "intro"); + introElem.innerText = script.intro; + controls.appendChild(introLabel); + controls.appendChild(introElem); + for(var i = 0; i < script.prompts.length; i++) { + var promptLabel = document.createElement("label"); + promptLabel.setAttribute("for", "prompt"+i); + promptLabel.setAttribute("id", "promptLabel"+i); + promptLabel.innerText = script.prompts[i].prompt; + promptLabel.setAttribute("class", "promptLabel"); + var promptNotes = document.createElement("p"); + promptNotes.innerText = script.prompts[i].notes; + promptNotes.setAttribute("class", "promptNotes"); + controls.appendChild(promptLabel); + controls.appendChild(promptNotes); + if(script.prompts[i].input.type == "text") { + var promptElem = document.createElement("textarea"); + promptElem.setAttribute("id", "prompt"+i); + promptElem.setAttribute("title", script.prompts[i].promptId); + promptElem.oninput = setVoterSaveStatus.bind(null,"unsaved"); + var val = null; + for(var j = 0; j < responses.length; j++) { + if(responses[j].promptId == script.prompts[i].promptId) { + val = responses[j].value; + } + } + promptElem.value = val; + controls.appendChild(promptElem); + } else if(script.prompts[i].input.type == "select") { + var promptElem = document.createElement("select"); + promptElem.setAttribute("id", "prompt"+i); + promptElem.setAttribute("title", script.prompts[i].promptId); + promptElem.oninput = setVoterSaveStatus.bind(null,"unsaved"); + var val = null; + for(var j = 0; j < responses.length; j++) { + if(responses[j].promptId == script.prompts[i].promptId) { + val = responses[j].value; + } + } + var opts = script.prompts[i].input.options; + for(var j = 0; j < opts.length; j++) { + var optElem = document.createElement("option"); + optElem.innerText = opts[j].text; + optElem.value = opts[j].value; + if(opts[j].value == val) { + optElem.selected = true; + } + promptElem.appendChild(optElem); + } + opts.value = val; + controls.appendChild(promptElem); + } + } } var notesLabel = document.createElement("label"); @@ -347,12 +484,12 @@ function showVoterInfo(details, results, custom, editable) { notesLabel.innerHTML = "Additional Notes:"; var notes = document.createElement("textarea"); notes.setAttribute("id", "voterNotes"); - if(results.notes == "undefined") { + if(results.notes == "undefined" || typeof results.notes == "undefined") { notes.innerHTML = ""; } else { notes.innerHTML = results.notes; } - notes.onchange = function() { document.getElementById("voterSave").classList.add("unsaved"); }; + notes.oninput = setVoterSaveStatus.bind(null,"unsaved"); controls.appendChild(notesLabel); controls.appendChild(notes); @@ -362,7 +499,7 @@ function showVoterInfo(details, results, custom, editable) { var checkbox = document.createElement("input"); checkbox.setAttribute("type", "checkbox"); checkbox.id = "correctionsCheck"; - checkbox.onchange = function() { document.getElementById("voterSave").classList.add("unsaved"); }; + checkbox.onchange = setVoterSaveStatus.bind(null,"unsaved"); if(results.corrections == 1) { checkbox.setAttribute("checked", true); } @@ -381,7 +518,7 @@ function showVoterInfo(details, results, custom, editable) { var cell = document.createElement("td"); var checkbox = document.createElement("input"); checkbox.setAttribute("type", "checkbox"); - checkbox.onchange = function() { document.getElementById("voterSave").classList.add("unsaved"); }; + checkbox.onchange = setVoterSaveStatus.bind(null,"unsaved"); if(results.priority == 1) { checkbox.setAttribute("checked", true); } @@ -402,7 +539,7 @@ function showVoterInfo(details, results, custom, editable) { var checkbox = document.createElement("input"); checkbox.setAttribute("type", "checkbox"); checkbox.id = "noContactCheck"; - checkbox.onchange = function() { document.getElementById("voterSave").classList.add("unsaved"); }; + checkbox.onchange = setVoterSaveStatus.bind(null,"unsaved"); if(results.dnc == 1) { checkbox.setAttribute("checked", true); } @@ -426,6 +563,10 @@ function showVoterInfo(details, results, custom, editable) { // cell.appendChild(save); // row.appendChild(cell); + var status = document.createElement("div"); + status.innerHTML = ""; + status.setAttribute("id", "voterSaveStatus"); + // var cell = document.createElement("td"); var back = document.createElement("button"); back.innerHTML = "back"; @@ -438,23 +579,47 @@ function showVoterInfo(details, results, custom, editable) { controls.appendChild(flagTable); controls.appendChild(save); + controls.appendChild(status); controls.appendChild(back); var mr = document.getElementById("mapReturn"); if(mr != null) { mr.parentElement.removeChild(mr); } + debug("End showVoterInfo"); +} + +// Called on any input change to set the status +function setVoterSaveStatus(status) { +console.log(status); + document.getElementById("voterSaveStatus").classList.remove("unsaved"); + document.getElementById("voterSaveStatus").classList.remove("saved"); + document.getElementById("voterSaveStatus").classList.remove("error"); + + document.getElementById("voterSaveStatus").classList.add(status); } // function saveVoterDetails() { + debug("saveVoterDetails()"); setLoading(1); var id = document.getElementById("voterId").value; - var supportPct = document.getElementById("voterSupportRange").value; +// var supportPct = document.getElementById("voterSupportRange").value; var notes = document.getElementById("voterNotes").value; var corrections = document.getElementById("correctionsCheck").checked; var priority = document.getElementById("priorityCheck").checked; var dnc = document.getElementById("noContactCheck").checked; + + var prompts = []; + var i = 0; + while( document.getElementById("prompt"+i) != null && i < 20) { + var promptObj = {}; + promptObj.promptId = document.getElementById("prompt"+i).title; + promptObj.value = document.getElementById("prompt"+i).value; + prompts.push(promptObj); + i++; + } + var newEntryBody = ""; var newEntryItems = document.getElementsByClassName("newVoterDetails"); if(newEntryItems.length > 0) { @@ -474,21 +639,33 @@ function saveVoterDetails() { var options = { method: "POST", headers: {"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"}, - body: "id="+id+"&supportPct="+supportPct+"¬es="+encodeURIComponent(notes)+ + body: "id="+id+"¬es="+encodeURIComponent(notes)+"&prompts="+encodeURIComponent(JSON.stringify(prompts))+ "&corrections="+corrections+"&priority="+priority+"&dnc="+dnc+"&canvassId="+canvassId+ newEntryBody }; fetch("api.php?get=set&set=canvassResult", options).then(data => data.json()) .then(resp => { - console.log(resp); + if(resp[0] == "00000") { + setVoterSaveStatus("saved"); + } else { + setVoterSaveStatus("error"); + } + map.invalidateSize(); + }) + .catch(error => { + setVoterSaveStatus("error"); + document.getElementById("voterSaveStatus").classList.add("error"); + console.log(error); }); - document.getElementById("voterSave").classList.remove("unsaved"); + //document.getElementById("voterSave").classList.remove("unsaved"); setLoading(-1); + debug("End saveVoterDetails"); } // Generate a string representing the voter selection function getVoterString() { + debug("getVoterString()"); var parties = ""; if(document.getElementById("voters-dem").checked) { parties += "Democrat"; @@ -541,11 +718,14 @@ function getVoterString() { } lists = lists.replace(/^,|,$/g, ''); + debug("End getVoterString"); return "parties="+parties+"&history="+history+"&affiliations="+affs+"&precincts="+precincts+"&lists="+lists; } // Configure voter selection from an existing voter string function setVoterString(voterStr) { + debug("setVoterString(...)"); + debug(voterStr); // Clear all precincts so we can update var precinctChecks = document.getElementsByClassName("precinct-check"); for(var i = 0; i < precinctChecks.length; i++) { @@ -589,4 +769,5 @@ function setVoterString(voterStr) { } } loadVoters(true); + debug("End setVoterString"); } diff --git a/login.php b/login.php index 5b95547..56be889 100644 --- a/login.php +++ b/login.php @@ -36,6 +36,10 @@ $options = [ $dbh = new PDO("mysql:host=localhost;dbname=CCCP", "root", "yix", $options); // Check if password is valid +if( isset($_GET['username']) && isset($_GET['password']) ) { + $_POST['username'] = $_GET['username']; + $_POST['password'] = $_GET['password']; +} if( isset($_POST) && sizeof($_POST) > 0 ) { $_POST['username']; $_POST['password']; @@ -50,7 +54,13 @@ if( isset($_POST) && sizeof($_POST) > 0 ) { $_SESSION['userId'] = $row[0]['id']; $_SESSION['authtime'] = time(); $_SESSION['permissions'] = $row[0]['permissions']; + + if( isset($_GET['target']) && $_GET['target'] == "canvass" ) { + header("Location: ./canvass.php"); + return; + } header("Location: ./index.php"); + return; } $auth_error = "Unable to login."; diff --git a/research.php b/research.php index fcdf870..29980df 100644 --- a/research.php +++ b/research.php @@ -27,8 +27,10 @@ $dbh = new PDO("mysql:host=localhost;dbname=CCCP", "root", "yix", $options); ?> + + @@ -126,7 +128,7 @@ $dbh = new PDO("mysql:host=localhost;dbname=CCCP", "root", "yix", $options);

Use the checkboxes below to display pins on the map for each category of voter.

To load fewer pins (which may load faster), select a turf first or zoom in. - A maximum of 3000 voters will be shown at once.

+ A maximum of 10000 voters will be shown at once.

diff --git a/script.json b/script.json new file mode 100644 index 0000000..f2efec0 --- /dev/null +++ b/script.json @@ -0,0 +1,30 @@ +{ + "intro": "Hi, my name is ___, I’m with the Rhode Island Democratic Socialists. We’re out here talking to people because we’re tired of how our one-party government prioritizes corporate profits over working-class Rhode Islanders. We want to build a new party that actually represents us.", + "prompts": [ + { + "promptId": "000", + "prompt": "What do you think are the main problems/failures of our local government?", + "notes": "Take note of any concerns that connect to our platform, and use those points to transition into talking about our goals or demands", + "input": {"type": "text", "default": null}, + "resources": [ + { + "href": "http://...", + "title": "Platform" + } + ] + }, + { + "promptId": "001", + "prompt": "Would you vote for an independent candidate who runs on a platform like the one on our palm card?", + "notes": "Use their response here to adjust the support drop-down above.", + "input": {"type": "select", + "options": [{"text": "Definite Support (1)", "value": 100}, + {"text": "Somewhat Support (2)", "value": 50}, + {"text": "Unconvinced (3)", "value": 0}, + {"text": "Somewhat Opposed (4)", "value": -50}, + {"text": "Definite Opposed (5)", "value": -100}] + }, + "resources": null + } + ] +} diff --git a/settings-api.php b/settings-api.php index fd7fbd7..d6bbbc7 100644 --- a/settings-api.php +++ b/settings-api.php @@ -20,7 +20,7 @@ CCCP_PERM_ADMIN ) { echo "Requires admin access.".PHP_EOL; die(); } diff --git a/settings.php b/settings.php index b543b18..96280ec 100644 --- a/settings.php +++ b/settings.php @@ -116,22 +116,39 @@ if( sizeof($rows) == 0) { } else { $STATUS['geocoder.remaining'] = $rows[0]['cnt']; } +$query = "select count(*) cnt FROM (select addressId, count(*) c FROM (select distinct addressId, engine from geocodeResults) gr group by addressId) gc WHERE c > 2;"; +$stmt = $dbh->prepare($query); +$stmt->execute(); +$rows = $stmt->fetchAll(); +if( sizeof($rows) == 0) { + $STATUS['geocoder.verified'] = "N/A"; +} else { + $STATUS['geocoder.verified'] = $rows[0]['cnt']; +} +$query = "select count(distinct addressId) cnt FROM voters;"; +$stmt = $dbh->prepare($query); +$stmt->execute(); +$rows = $stmt->fetchAll(); +if( sizeof($rows) == 0) { + $STATUS['geocoder.total'] = "N/A"; +} else { + $STATUS['geocoder.total'] = $rows[0]['cnt']; +} + -/*$STATUS['geocoder.maxDaily'] = 1; -$STATUS['geocoder.maxDaily'] += $STATUS['geocoder.geoapify'] == "ON" ? $CONFIG['geocoder.geoapify.maxdaily'] : 0; -$STATUS['geocoder.maxDaily'] += $STATUS['geocoder.maps.co'] == "ON" ? $CONFIG['geocoder.maps.co.maxdaily'] : 0; -$STATUS['geocoder.maxDaily'] += $STATUS['geocoder.tomtom'] == "ON" ? $CONFIG['geocoder.tomtom.maxdaily'] : 0;*/ $query = "select count(*) cnt from geocodeResults where latitude IS NOT NULL ". "AND updateDate > DATE_SUB(CURDATE(), INTERVAL 1 DAY);"; $stmt = $dbh->prepare($query); $stmt->execute(); $rows = $stmt->fetchAll(); -//print_r($rows[0]['cnt']); -if( sizeof($rows) == 0) { +if( sizeof($rows) == 0 || $rows[0]['cnt'] == 0) { $STATUS['geocoder.maxDaily'] = "N/A"; } else { $STATUS['geocoder.maxDaily'] = $rows[0]['cnt']; } +if($STATUS['geocoder.maxDaily'] == 0) { + $STATUS['geocoder.maxDaily'] = 1; +} @@ -139,6 +156,7 @@ if( sizeof($rows) == 0) { + @@ -273,10 +291,16 @@ for($i = $STATUS['cv.list.min']; $i <= $STATUS['cv.list.max']; $i++) {
2) { + $lastCat = str_replace(".png", "", explode("-", $files[0])[2]); +} +//$lastCat = ""; foreach($files as $file) { if( strpos($file, "marker-icon-") === 0 ) { $category = str_replace(".png", "", explode("-", $file)[2]); +// if($lastCat == "") { $lastCat = $category; } if($category != $lastCat) { if($file != $files[0]) { echo "
"; @@ -444,11 +468,18 @@ for($i = 0; $i < sizeof($rows); $i++) { + + + + +
Records Remaining: -
Records Verified: + / +
diff --git a/taskRunner.php b/taskRunner.php index 12456af..90b07ee 100644 --- a/taskRunner.php +++ b/taskRunner.php @@ -757,17 +757,37 @@ for($i = 0; $i < sizeof($tasks); $i++) { // Run (approximately) one hour's worth of geocodes $updated = $tasks[$i]['taskStep']; // Select the next max addresses; just don't worry about parents being in the same batch + $mode = "scan"; $query = "select va.id as id, va.addressLine1, va.addressLine2, va.city, va.state, va.zip ". - "from voterAddresses va, voters v WHERE v.addressId = va.id AND va.latitude IS NULL ". + "from voterAddresses va WHERE va.latitude IS NULL ". "AND (SELECT id FROM geocodeResults WHERE engine=? AND addressId=va.id LIMIT 1) IS NULL ". "order by va.id desc limit ?;"; $stmt = $dbh->prepare($query); $stmt->execute( Array($tasks[$i]['taskName'], $maxGeocodes) ); $geocodes = $stmt->fetchAll(); + // If there's no addresses with NO lat/lon data, re-scan any that were scanned with another engine + if( sizeof($geocodes) <= 100 ) { + echo "No urgent geocodes.".PHP_EOL; + $mode = "verify"; + $query = "select va.id as id, va.addressLine1, va.addressLine2, va.city, va.state, va.zip ". + "from voterAddresses va WHERE ". + "(SELECT id FROM geocodeResults WHERE engine=? AND addressId=va.id LIMIT 1) IS NULL ". + "order by va.id desc limit ?;"; + $stmt = $dbh->prepare($query); + $stmt->execute( Array($tasks[$i]['taskName'], $maxGeocodes) ); + $geocodes = array_merge($geocodes, $stmt->fetchAll()); + } if( sizeof($geocodes) == 0 ) { - echo "No pending geocodes"; + echo "No pending geocodes".PHP_EOL; continue; } + // Close the task if we will be over 100% + if( sizeof($geocodes) < $maxCount ) { + $query = "UPDATE CCCP.tasks SET taskComplete=CURRENT_TIMESTAMP, taskLastStep=? WHERE id = ?;"; + $params = Array(sizeof($geocodes), $tid); + $stmt = $dbh->prepare($query); + $stmt->execute($params); + } // Run the geocode for($g = 0; $g < $maxCount && $g < sizeof($geocodes); $g++) { @@ -794,6 +814,12 @@ for($i = 0; $i < sizeof($tasks); $i++) { if( !isset($resultsObj->features[0]->properties->lat) ) { echo "ERROR: Invalid response returned from ".$tasks[$i]['taskName'].PHP_EOL; print_r($resultsObj); + + // Log the geocode result + $query = "INSERT INTO geocodeResults(engine, addressId, latitude, longitude, response) VALUES(?,?,?,?,?);"; + $params= Array($tasks[$i]['taskName'], $geocodes[$g]['id'], null, null, json_encode($resultsObj)); + $stmt = $dbh->prepare($query); + $stmt->execute($params); continue 1; } @@ -822,12 +848,24 @@ for($i = 0; $i < sizeof($tasks); $i++) { // Maps.co is less effective at parsing certain addresses, so it's likely to get no results if( !is_array($resultsObj) || sizeof($resultsObj) == 0) { echo "No Maps.co results for address ID: ".$geocodes[$g]['id'].PHP_EOL; + + // Log the geocode result + $query = "INSERT INTO geocodeResults(engine, addressId, latitude, longitude, response) VALUES(?,?,?,?,?);"; + $params= Array($tasks[$i]['taskName'], $geocodes[$g]['id'], null, null, json_encode($resultsObj)); + $stmt = $dbh->prepare($query); + $stmt->execute($params); continue; } if( !isset($resultsObj[0]->lat) ) { echo "ERROR: Invalid response returned from ".$tasks[$i]['taskName'].PHP_EOL; print_r($resultsObj); + + // Log the geocode result + $query = "INSERT INTO geocodeResults(engine, addressId, latitude, longitude, response) VALUES(?,?,?,?,?);"; + $params= Array($tasks[$i]['taskName'], $geocodes[$g]['id'], null, null, json_encode($resultsObj)); + $stmt = $dbh->prepare($query); + $stmt->execute($params); continue; } @@ -848,6 +886,12 @@ for($i = 0; $i < sizeof($tasks); $i++) { // Maps.co is less effective at parsing certain addresses, so it's likely to get no results if( !is_array($resultsObj->results) || sizeof($resultsObj->results) == 0) { echo "No TomTom results for address ID: ".$geocodes[$g]['id']; + + // Log the geocode result + $query = "INSERT INTO geocodeResults(engine, addressId, latitude, longitude, response) VALUES(?,?,?,?,?);"; + $params= Array($tasks[$i]['taskName'], $geocodes[$g]['id'], null, null, json_encode($resultsObj)); + $stmt = $dbh->prepare($query); + $stmt->execute($params); continue; } @@ -855,6 +899,12 @@ for($i = 0; $i < sizeof($tasks); $i++) { echo "ERROR: Invalid response returned from ".$tasks[$i]['taskName'].PHP_EOL; print_r($resultsObj); print_r($geocodes[$g]); + + // Log the geocode result + $query = "INSERT INTO geocodeResults(engine, addressId, latitude, longitude, response) VALUES(?,?,?,?,?);"; + $params= Array($tasks[$i]['taskName'], $geocodes[$g]['id'], null, null, json_encode($resultsObj)); + $stmt = $dbh->prepare($query); + $stmt->execute($params); continue; } @@ -862,6 +912,12 @@ for($i = 0; $i < sizeof($tasks); $i++) { echo "ERROR: Invalid address returned from ".$tasks[$i]['taskName'].PHP_EOL; print_r($resultsObj); print_r($geocodes[$g]); + + // Log the geocode result + $query = "INSERT INTO geocodeResults(engine, addressId, latitude, longitude, response) VALUES(?,?,?,?,?);"; + $params= Array($tasks[$i]['taskName'], $geocodes[$g]['id'], null, null, json_encode($resultsObj)); + $stmt = $dbh->prepare($query); + $stmt->execute($params); continue; }