You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

221 line
7.9KB

  1. <?php
  2. /**
  3. * @var \App\View\AppView $this
  4. * @var iterable<\App\Model\Entity\Territory> $territories
  5. */
  6. $this->assign('title', 'Territories');
  7. $territoriesData = [];
  8. foreach ($territories as $territory) {
  9. $addresses = [];
  10. foreach ($territory->addresses as $address) {
  11. $addresses[] = [
  12. 'id' => $address->id,
  13. 'full_address' => $address->full_address,
  14. 'lat' => (float)$address->lat,
  15. 'lng' => (float)$address->lng,
  16. ];
  17. }
  18. $territoriesData[] = [
  19. 'id' => $territory->id,
  20. 'name' => $territory->name,
  21. 'polygon' => $territory->polygon,
  22. 'addresses' => $addresses,
  23. ];
  24. }
  25. $territoriesJson = str_replace('</', '<\/', (string)json_encode($territoriesData));
  26. $mapVar = $this->Leaflet->name();
  27. ?>
  28. <div class="content">
  29. <div class="page-header">
  30. <h1>Territories</h1>
  31. <p>Use the polygon tool on the map to draw a new territory. Use the edit/delete tools to update or remove one. Use "Find addresses" to pull home addresses inside a territory from OpenStreetMap.</p>
  32. </div>
  33. <?= $this->Leaflet->map(['div' => ['id' => 'territories-map', 'height' => '600px']]) ?>
  34. <?php
  35. // Must be registered after Leaflet->map() so leaflet.js (which leaflet-draw.js extends) loads first.
  36. ?>
  37. <?= $this->Html->css('https://unpkg.com/leaflet-draw@1.0.4/dist/leaflet.draw.css', ['block' => true]) ?>
  38. <?= $this->Html->script('https://unpkg.com/leaflet-draw@1.0.4/dist/leaflet.draw.js', ['block' => true]) ?>
  39. <?php
  40. $this->Leaflet->addCustom(<<<JS
  41. var drawnItems = new L.FeatureGroup();
  42. {$mapVar}.addLayer(drawnItems);
  43. var addressIcon = L.divIcon({
  44. className: 'address-marker-icon',
  45. html: '⌂',
  46. iconSize: [22, 22],
  47. iconAnchor: [11, 11]
  48. });
  49. var addressLayers = {};
  50. function renderAddresses(territoryId, addresses) {
  51. if (addressLayers[territoryId]) {
  52. {$mapVar}.removeLayer(addressLayers[territoryId]);
  53. }
  54. var group = L.layerGroup();
  55. addresses.forEach(function (a) {
  56. L.marker([a.lat, a.lng], { icon: addressIcon })
  57. .bindPopup(a.full_address)
  58. .addTo(group);
  59. });
  60. group.addTo({$mapVar});
  61. addressLayers[territoryId] = group;
  62. }
  63. var territoryData = {$territoriesJson};
  64. territoryData.forEach(function (t) {
  65. var layer = L.geoJSON(t.polygon).getLayers()[0];
  66. layer.territoryId = t.id;
  67. layer.bindPopup(t.name);
  68. drawnItems.addLayer(layer);
  69. renderAddresses(t.id, t.addresses);
  70. });
  71. var drawControl = new L.Control.Draw({
  72. draw: {
  73. polygon: true,
  74. polyline: false,
  75. rectangle: false,
  76. circle: false,
  77. circlemarker: false,
  78. marker: false
  79. },
  80. edit: {
  81. featureGroup: drawnItems
  82. }
  83. });
  84. {$mapVar}.addControl(drawControl);
  85. var csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
  86. function territoryApiRequest(url, method, body) {
  87. return fetch(url, {
  88. method: method,
  89. headers: {
  90. 'Content-Type': 'application/json',
  91. 'X-CSRF-Token': csrfToken,
  92. 'Accept': 'application/json'
  93. },
  94. body: JSON.stringify(body)
  95. }).then(function (res) { return res.json(); });
  96. }
  97. {$mapVar}.on('draw:created', function (e) {
  98. var layer = e.layer;
  99. var name = window.prompt('Name this territory:');
  100. if (!name) {
  101. return;
  102. }
  103. territoryApiRequest('/territories/add', 'POST', {
  104. name: name,
  105. polygon: layer.toGeoJSON().geometry
  106. }).then(function (res) {
  107. if (res.success) {
  108. layer.territoryId = res.data.id;
  109. layer.bindPopup(res.data.name);
  110. drawnItems.addLayer(layer);
  111. } else {
  112. alert('Could not save territory.');
  113. }
  114. });
  115. });
  116. {$mapVar}.on('draw:edited', function (e) {
  117. e.layers.eachLayer(function (layer) {
  118. if (!layer.territoryId) {
  119. return;
  120. }
  121. territoryApiRequest('/territories/edit/' + layer.territoryId, 'POST', {
  122. polygon: layer.toGeoJSON().geometry
  123. }).then(function (res) {
  124. if (!res.success) {
  125. alert('Could not update territory.');
  126. }
  127. });
  128. });
  129. });
  130. {$mapVar}.on('draw:deleted', function (e) {
  131. e.layers.eachLayer(function (layer) {
  132. if (!layer.territoryId) {
  133. return;
  134. }
  135. territoryApiRequest('/territories/delete/' + layer.territoryId, 'POST', {}).then(function (res) {
  136. if (!res.success) {
  137. alert('Could not delete territory.');
  138. }
  139. });
  140. });
  141. });
  142. document.addEventListener('click', function (e) {
  143. var button = e.target.closest('.js-find-addresses');
  144. if (!button) {
  145. return;
  146. }
  147. var territoryId = button.getAttribute('data-territory-id');
  148. var countEl = document.querySelector('.js-address-count[data-territory-id="' + territoryId + '"]');
  149. button.disabled = true;
  150. button.textContent = 'Searching…';
  151. territoryApiRequest('/territories/addresses/' + territoryId, 'POST', {}).then(function (res) {
  152. button.disabled = false;
  153. button.textContent = 'Find addresses';
  154. if (res.success) {
  155. renderAddresses(territoryId, res.data.addresses.map(function (a) {
  156. return { full_address: a.full_address, lat: parseFloat(a.lat), lng: parseFloat(a.lng) };
  157. }));
  158. if (countEl) {
  159. countEl.textContent = res.data.addresses.length;
  160. }
  161. } else {
  162. alert('Could not find addresses: ' + JSON.stringify(res.errors));
  163. }
  164. });
  165. });
  166. JS);
  167. $this->Leaflet->finalize();
  168. ?>
  169. <div class="territory-list">
  170. <h2>Saved territories</h2>
  171. <?php if (!$territoriesData): ?>
  172. <p class="territory-list-empty">No territories yet &mdash; draw one on the map above.</p>
  173. <?php else: ?>
  174. <table class="territory-table">
  175. <thead>
  176. <tr>
  177. <th>Name</th>
  178. <th>Created</th>
  179. <th>Addresses</th>
  180. <th></th>
  181. </tr>
  182. </thead>
  183. <tbody>
  184. <?php foreach ($territories as $territory): ?>
  185. <tr>
  186. <td><?= h($territory->name) ?></td>
  187. <td><?= h($territory->created) ?></td>
  188. <td>
  189. <span class="js-address-count" data-territory-id="<?= $territory->id ?>">
  190. <?= count($territory->addresses) ?>
  191. </span>
  192. </td>
  193. <td>
  194. <button type="button" class="button-link js-find-addresses" data-territory-id="<?= $territory->id ?>">Find addresses</button>
  195. </td>
  196. </tr>
  197. <?php endforeach; ?>
  198. </tbody>
  199. </table>
  200. <?php endif; ?>
  201. </div>
  202. </div>

Powered by TurnKey Linux.