@fleetbase/fleetops-engine 0.6.44 → 0.6.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. package/addon/components/admin/map-settings.hbs +26 -0
  2. package/addon/components/admin/map-settings.js +46 -0
  3. package/addon/components/admin/routing-settings.hbs +11 -0
  4. package/addon/components/cell/order-route-type.hbs +46 -0
  5. package/addon/components/cell/order-route-type.js +90 -0
  6. package/addon/components/cell/place-address.hbs +1 -0
  7. package/addon/components/display-place.hbs +45 -56
  8. package/addon/components/display-place.js +21 -0
  9. package/addon/components/entity/form.hbs +24 -0
  10. package/addon/components/entity/form.js +30 -0
  11. package/addon/components/layout/fleet-ops-sidebar/driver-listing.js +5 -5
  12. package/addon/components/layout/fleet-ops-sidebar/fleet-listing.js +5 -5
  13. package/addon/components/layout/fleet-ops-sidebar.js +8 -0
  14. package/addon/components/leaflet-draw-control.js +1 -1
  15. package/addon/components/maintenance-schedule/form.js +2 -1
  16. package/addon/components/map/drawer/driver-listing.js +5 -5
  17. package/addon/components/map/drawer/geofence-event-listing.js +5 -2
  18. package/addon/components/map/drawer/place-listing.js +5 -5
  19. package/addon/components/map/drawer/position-listing.js +102 -21
  20. package/addon/components/map/drawer/vehicle-listing.js +5 -5
  21. package/addon/components/map/google-live-map.hbs +9 -0
  22. package/addon/components/map/google-live-map.js +445 -0
  23. package/addon/components/map/leaflet-live-map.hbs +186 -142
  24. package/addon/components/map/leaflet-live-map.js +121 -41
  25. package/addon/components/map/toolbar/visibility-control-panel.js +2 -2
  26. package/addon/components/map/toolbar/zones-panel.hbs +9 -9
  27. package/addon/components/map/toolbar.hbs +3 -3
  28. package/addon/components/map/toolbar.js +9 -1
  29. package/addon/components/modals/map-layer-form.hbs +132 -63
  30. package/addon/components/modals/orchestrator-import.hbs +4 -11
  31. package/addon/components/modals/orchestrator-import.js +17 -1
  32. package/addon/components/modals/update-order-activity.hbs +1 -1
  33. package/addon/components/orchestrator/order-pool.js +3 -2
  34. package/addon/components/orchestrator-workbench.js +29 -41
  35. package/addon/components/order/details/detail.js +2 -2
  36. package/addon/components/order/details/payload.hbs +5 -5
  37. package/addon/components/order/details/payload.js +18 -1
  38. package/addon/components/order/details/route.hbs +2 -2
  39. package/addon/components/order/details/route.js +76 -3
  40. package/addon/components/order/form/payload.hbs +33 -0
  41. package/addon/components/order/form/payload.js +1 -0
  42. package/addon/components/order/form/route.hbs +8 -8
  43. package/addon/components/order/form/route.js +78 -18
  44. package/addon/components/order/form/service-rate.hbs +1 -1
  45. package/addon/components/order/form.js +2 -2
  46. package/addon/components/order/route-editor.hbs +209 -147
  47. package/addon/components/order/route-editor.js +143 -102
  48. package/addon/components/place/address.hbs +1 -0
  49. package/addon/components/place/address.js +12 -0
  50. package/addon/components/route-list.hbs +43 -55
  51. package/addon/components/route-list.js +47 -0
  52. package/addon/components/route-optimization-wizard-panel.js +3 -4
  53. package/addon/components/service-rate/details.hbs +6 -6
  54. package/addon/components/service-rate/form.hbs +28 -15
  55. package/addon/components/vehicle/card.hbs +4 -4
  56. package/addon/controllers/maintenance/schedules/index.js +2 -1
  57. package/addon/controllers/operations/orders/index/details/virtual.js +5 -0
  58. package/addon/controllers/operations/orders/index/details.js +113 -15
  59. package/addon/controllers/operations/orders/index/new.js +1 -1
  60. package/addon/controllers/operations/orders/index.js +8 -1
  61. package/addon/controllers/operations/service-rates/index/edit.js +1 -0
  62. package/addon/controllers/operations/service-rates/index/new.js +1 -0
  63. package/addon/controllers/settings/map.js +97 -0
  64. package/addon/controllers/settings/routing.js +47 -12
  65. package/addon/extension.js +11 -0
  66. package/addon/helpers/f-to-int.js +6 -0
  67. package/addon/helpers/place-address-html.js +6 -0
  68. package/addon/instance-initializers/register-google-routes.js +17 -0
  69. package/addon/instance-initializers/register-osrm.js +5 -0
  70. package/addon/routes/application.js +9 -3
  71. package/addon/routes/operations/orchestrator.js +13 -0
  72. package/addon/routes/operations/orders/index/details/virtual.js +1 -0
  73. package/addon/routes/operations/orders/index/details.js +47 -24
  74. package/addon/routes/operations/orders/index/new.js +9 -0
  75. package/addon/routes/operations/orders/index.js +0 -1
  76. package/addon/routes/settings/map.js +3 -0
  77. package/addon/routes.js +1 -0
  78. package/addon/services/entity-actions.js +1 -1
  79. package/addon/services/equipment-actions.js +4 -6
  80. package/addon/services/geofence-event-bus.js +2 -1
  81. package/addon/services/geofence.js +204 -168
  82. package/addon/services/google-routes.js +300 -0
  83. package/addon/services/leaflet-draw-restriction.js +2 -1
  84. package/addon/services/leaflet-map-manager.js +21 -0
  85. package/addon/services/maintenance-actions.js +4 -6
  86. package/addon/services/map-adapter/google.js +2205 -0
  87. package/addon/services/map-adapter/leaflet.js +1042 -0
  88. package/addon/services/map-adapter-interface.js +551 -0
  89. package/addon/services/map-manager.js +768 -0
  90. package/addon/services/map-settings.js +75 -0
  91. package/addon/services/movement-tracker.js +83 -32
  92. package/addon/services/order-actions.js +11 -1
  93. package/addon/services/osrm.js +79 -6
  94. package/addon/services/part-actions.js +4 -6
  95. package/addon/services/position-playback.js +135 -62
  96. package/addon/services/route-engine.js +83 -0
  97. package/addon/services/service-areas.js +8 -1
  98. package/addon/services/service-rate-actions.js +8 -3
  99. package/addon/styles/fleetops-engine.css +555 -0
  100. package/addon/templates/management/vehicles/index.hbs +0 -1
  101. package/addon/templates/operations/orders/index/details/virtual.hbs +7 -1
  102. package/addon/templates/operations/orders/index/details.hbs +0 -1
  103. package/addon/templates/settings/map.hbs +53 -0
  104. package/addon/templates/settings/routing.hbs +15 -4
  105. package/addon/utils/fleet-ops-options.js +34 -0
  106. package/addon/utils/has-valid-coordinates.js +6 -0
  107. package/addon/utils/map-provider-options.js +6 -0
  108. package/addon/utils/order-route-editing.js +86 -0
  109. package/addon/utils/order-route-summary.js +20 -0
  110. package/addon/utils/place-address-html.js +41 -0
  111. package/addon/utils/route-colors.js +43 -12
  112. package/addon/utils/route-visualization.js +93 -0
  113. package/addon/utils/serialize-payload.js +1 -1
  114. package/addon/utils/vendor-integration.js +2 -1
  115. package/app/components/admin/map-settings.js +1 -0
  116. package/app/components/admin/routing-settings.js +1 -0
  117. package/app/components/cell/order-route-type.js +1 -0
  118. package/app/components/cell/place-address.js +1 -0
  119. package/app/components/map/google-live-map.js +1 -0
  120. package/app/components/place/address.js +1 -0
  121. package/app/controllers/operations/orders/index/details/virtual.js +1 -0
  122. package/app/controllers/settings/map.js +1 -0
  123. package/app/helpers/f-to-int.js +1 -0
  124. package/app/helpers/place-address-html.js +1 -0
  125. package/app/routes/settings/map.js +1 -0
  126. package/app/services/map-adapter/google.js +1 -0
  127. package/app/services/map-adapter/leaflet.js +1 -0
  128. package/app/services/map-adapter-interface.js +1 -0
  129. package/app/services/map-manager.js +1 -0
  130. package/app/services/map-settings.js +1 -0
  131. package/app/templates/settings/map.js +1 -0
  132. package/app/utils/has-valid-coordinates.js +1 -0
  133. package/app/utils/order-route-editing.js +2 -0
  134. package/app/utils/order-route-summary.js +1 -0
  135. package/app/utils/place-address-html.js +1 -0
  136. package/app/utils/route-colors.js +1 -1
  137. package/app/utils/route-visualization.js +1 -0
  138. package/assets/images/drawing-controls-spritesheet.svg +156 -0
  139. package/composer.json +1 -1
  140. package/extension.json +1 -1
  141. package/package.json +3 -3
  142. package/server/src/Auth/Schemas/FleetOps.php +5 -0
  143. package/server/src/Http/Controllers/Api/v1/OrderController.php +36 -8
  144. package/server/src/Http/Controllers/Api/v1/PayloadController.php +14 -1
  145. package/server/src/Http/Controllers/Api/v1/PurchaseRateController.php +11 -3
  146. package/server/src/Http/Controllers/Api/v1/ServiceAreaController.php +2 -0
  147. package/server/src/Http/Controllers/Api/v1/ServiceQuoteController.php +7 -2
  148. package/server/src/Http/Controllers/Api/v1/ServiceRateController.php +6 -1
  149. package/server/src/Http/Controllers/Api/v1/ZoneController.php +2 -0
  150. package/server/src/Http/Controllers/Internal/v1/LiveController.php +2 -1
  151. package/server/src/Http/Controllers/Internal/v1/OrchestrationController.php +7 -4
  152. package/server/src/Http/Controllers/Internal/v1/OrderController.php +61 -34
  153. package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +3 -2
  154. package/server/src/Http/Controllers/Internal/v1/SettingController.php +106 -3
  155. package/server/src/Http/Filter/OrderFilter.php +2 -1
  156. package/server/src/Http/Requests/CreateServiceRateRequest.php +2 -2
  157. package/server/src/Http/Resources/v1/Index/Payload.php +9 -7
  158. package/server/src/Http/Resources/v1/Order.php +20 -2
  159. package/server/src/Http/Resources/v1/ServiceQuote.php +2 -2
  160. package/server/src/Models/Entity.php +7 -0
  161. package/server/src/Models/Order.php +81 -8
  162. package/server/src/Models/OrderConfig.php +15 -0
  163. package/server/src/Models/Payload.php +131 -27
  164. package/server/src/Models/Place.php +182 -48
  165. package/server/src/Models/ServiceRate.php +125 -32
  166. package/server/src/Observers/PurchaseRateObserver.php +3 -5
  167. package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +59 -23
  168. package/server/src/Rules/ComputableAlgo.php +1 -3
  169. package/server/src/Support/Algo.php +87 -1
  170. package/server/src/Support/Utils.php +69 -0
  171. package/server/src/routes.php +4 -0
  172. package/translations/en-us.yaml +1937 -1916
@@ -1,67 +1,55 @@
1
+ /**
2
+ * GeofenceService
3
+ *
4
+ * Refactored to use the provider-agnostic MapManagerService for all map
5
+ * interactions (drawing, fitBounds, showDrawControl, etc.).
6
+ *
7
+ */
1
8
  import Service, { inject as service } from '@ember/service';
2
9
  import { action } from '@ember/object';
3
10
  import { debug } from '@ember/debug';
4
- import { createGeoJsonFromLayer } from '../utils/leaflet-to-geojson';
11
+ import { dasherize } from '@ember/string';
5
12
  import toMultiPolygon from '../utils/to-multi-polygon';
6
13
 
7
14
  export default class GeofenceService extends Service {
8
- @service leafletMapManager;
9
- @service leafletLayerVisibilityManager;
15
+ @service mapManager;
16
+ @service store;
17
+ @service modalsManager;
18
+ @service serviceAreas;
10
19
  @service serviceAreaActions;
11
20
  @service zoneActions;
12
21
  @service notifications;
13
22
  @service intl;
14
- #currentEdit = null;
23
+ _draftSession = null;
24
+ _draftCreatedHandler = null;
15
25
 
16
26
  @action createServiceArea() {
17
27
  this.notifications.info(this.intl.t('geofence.prompts.use-draw-controls-create-service-area'));
18
- this.leafletMapManager.showDrawControl();
19
- this.leafletMapManager.map.once('draw:created', ({ layer, layerType }) => {
20
- const border = toMultiPolygon(createGeoJsonFromLayer(layer, { layerType }));
21
-
22
- this.serviceAreaActions.modal.create(
23
- { border },
24
- {
25
- saveOptions: {
26
- callback: (serviceArea) => {
27
- this.leafletMapManager.hideDrawControl();
28
- this.#showServiceArea(serviceArea);
29
- },
30
- },
31
- }
32
- );
33
- });
28
+ this.#startDraftSession('service-area');
34
29
  }
35
30
 
36
31
  @action createZone(serviceArea) {
37
32
  this.notifications.info(this.intl.t('geofence.prompts.use-draw-controls-create-zone'));
38
- this.leafletMapManager.showDrawControl();
39
- this.leafletMapManager.map.fitBounds(serviceArea.leafletCoordinates, {
40
- paddingBottomRight: [300, 0],
41
- maxZoom: 15,
42
- animate: true,
43
- });
44
- // @todo use restriction service to restrict drawing to polygon
45
- // this.leafletDrawRestriction.setDrawRestrictionPolygon(serviceArea.leafletLayer);
46
- this.leafletMapManager.map.once('draw:created', ({ layer, layerType }) => {
47
- const border = createGeoJsonFromLayer(layer, { layerType });
48
33
 
49
- this.zoneActions.modal.create(
50
- { border, service_area_uuid: serviceArea.id },
51
- {
52
- saveOptions: {
53
- callback: (zone) => {
54
- this.leafletMapManager.hideDrawControl();
55
- this.#showZone(zone);
56
- },
57
- },
58
- }
59
- );
60
- });
34
+ if (serviceArea.leafletCoordinates) {
35
+ this.mapManager.fitBounds(serviceArea.leafletCoordinates, { paddingBottomRight: [300, 0], maxZoom: 15, animate: true });
36
+ }
37
+
38
+ this.#startDraftSession('zone', { serviceArea });
39
+ }
40
+
41
+ @action toggleDrawControl() {
42
+ if (this._draftSession) {
43
+ this.#resetDraftSession({ hideControl: true, removeDraft: true });
44
+ return;
45
+ }
46
+
47
+ this.notifications.info(this.intl.t('geofence.prompts.use-draw-controls-create-service-area'));
48
+ this.#startDraftSession('generic');
61
49
  }
62
50
 
63
51
  @action async editServiceArea(serviceArea) {
64
- const layer = serviceArea?.leafletLayer;
52
+ const layer = serviceArea?.leafletLayer ?? this.mapManager.getOverlay(serviceArea.id);
65
53
  if (!layer) {
66
54
  this.notifications.info(this.intl.t('geofence.prompts.no-layer-found-for-resource', { resource: this.intl.t('resource.service-area') }));
67
55
  return;
@@ -72,7 +60,7 @@ export default class GeofenceService extends Service {
72
60
  const result = await this.#editPolygonLayer(layer, { focusBounds: serviceArea.leafletCoordinates });
73
61
 
74
62
  if (result?.type === 'edited') {
75
- const border = toMultiPolygon(createGeoJsonFromLayer(result.layer));
63
+ const border = toMultiPolygon(result.geoJson ?? result.toGeoJSON?.());
76
64
  serviceArea.set('border', border);
77
65
  await serviceArea.save?.();
78
66
 
@@ -87,7 +75,7 @@ export default class GeofenceService extends Service {
87
75
  }
88
76
 
89
77
  @action async editZone(zone) {
90
- const layer = zone?.leafletLayer;
78
+ const layer = zone?.leafletLayer ?? this.mapManager.getOverlay(zone.id);
91
79
  if (!layer) {
92
80
  this.notifications.info(this.intl.t('geofence.prompts.no-layer-found-for-resource', { resource: this.intl.t('resource.zone') }));
93
81
  return;
@@ -98,7 +86,7 @@ export default class GeofenceService extends Service {
98
86
  const result = await this.#editPolygonLayer(layer, { focusBounds: zone.leafletCoordinates });
99
87
 
100
88
  if (result?.type === 'edited') {
101
- const border = createGeoJsonFromLayer(result.layer);
89
+ const border = result.geoJson ?? result.toGeoJSON?.();
102
90
  zone.set('border', border);
103
91
  await zone.save?.();
104
92
 
@@ -125,161 +113,209 @@ export default class GeofenceService extends Service {
125
113
  }
126
114
 
127
115
  @action focusServiceArea(serviceArea) {
128
- this.#showServiceArea(serviceArea);
129
- this.leafletMapManager.map.fitBounds(serviceArea.leafletCoordinates, {
130
- paddingBottomRight: [0, 0],
131
- maxZoom: 15,
132
- animate: true,
133
- });
116
+ this.#showServiceArea(serviceArea, { pin: true });
117
+ if (serviceArea.leafletCoordinates) {
118
+ this.mapManager.fitBounds(serviceArea.leafletCoordinates, { paddingBottomRight: [0, 0], maxZoom: 15, animate: true });
119
+ }
134
120
  }
135
121
 
136
122
  @action blurServiceArea(serviceArea) {
123
+ this.#showServiceArea(serviceArea, { pin: false });
137
124
  this.#hideServiceArea(serviceArea);
138
125
  }
139
126
 
140
127
  @action focusZone(zone) {
141
- this.leafletMapManager.showLayer(zone.leafletLayer);
142
- this.leafletMapManager.map.fitBounds(zone.leafletCoordinates, {
143
- paddingBottomRight: [0, 0],
144
- maxZoom: 15,
145
- animate: true,
128
+ this.#showZone(zone, { pin: true });
129
+ if (zone.leafletCoordinates) {
130
+ this.mapManager.fitBounds(zone.leafletCoordinates, { paddingBottomRight: [0, 0], maxZoom: 15, animate: true });
131
+ }
132
+ }
133
+
134
+ #startDraftSession(mode, { serviceArea = null } = {}) {
135
+ this.#resetDraftSession();
136
+
137
+ this._draftSession = {
138
+ mode,
139
+ serviceArea,
140
+ draftLayer: null,
141
+ };
142
+
143
+ this.mapManager.showDrawControl({
144
+ tools: ['polygon', 'circle', 'rectangle'],
145
+ allowEdit: true,
146
+ allowDelete: true,
147
+ defaultMode: null,
146
148
  });
149
+
150
+ this._draftCreatedHandler = (payload) => {
151
+ this.#handleDraftCreated(payload);
152
+ };
153
+
154
+ this.mapManager.on('draw:created', this._draftCreatedHandler);
147
155
  }
148
156
 
149
- /* eslint-disable no-empty */
150
- async #editPolygonLayer(originalLayer, { focusBounds = null } = {}) {
151
- const map = this.leafletMapManager.map;
152
- const draw = this.leafletMapManager.drawControl;
153
- const group = this.leafletMapManager.drawControlFeatureGroup;
154
- if (!map || !draw || !group || !originalLayer) {
155
- throw new Error('Geofence edit: map/draw/editGroup/layer not available');
156
- }
157
+ #handleDraftCreated({ layer, geoJson }) {
158
+ if (!this._draftSession) return;
157
159
 
158
- // Make layer visible if it wasn'e
159
- this.leafletLayerVisibilityManager.showLayer(originalLayer);
160
-
161
- // end any previous edit session cleanly
162
- if (this._currentEdit) {
163
- try {
164
- draw?._toolbars?.edit?._modes?.edit?.handler?.disable();
165
- } catch {}
166
- try {
167
- group.removeLayer(this._currentEdit.proxy);
168
- } catch {}
169
- // restore visibility of previous original
170
- if (this._currentEdit.wasVisible !== false) {
171
- this.leafletLayerVisibilityManager.showLayer(this._currentEdit.original);
172
- }
173
- this._currentEdit = null;
160
+ this._draftSession.draftLayer = layer;
161
+ this.mapManager.off('draw:created', this._draftCreatedHandler);
162
+ this._draftCreatedHandler = null;
163
+
164
+ if (this._draftSession.mode === 'service-area') {
165
+ const border = toMultiPolygon(geoJson);
166
+ this.serviceAreaActions.modal.create(
167
+ { border },
168
+ {
169
+ decline: (modal) => {
170
+ this.#resetDraftSession({ hideControl: true, removeDraft: true });
171
+ modal.done?.();
172
+ },
173
+ },
174
+ {
175
+ callback: (serviceArea) => {
176
+ this.#resetDraftSession({ hideControl: true, removeDraft: true });
177
+ this.#showServiceArea(serviceArea);
178
+ },
179
+ }
180
+ );
181
+ return;
174
182
  }
175
183
 
176
- // Build a lightweight editable clone ("proxy") — DO NOT re-parent the original
177
- // Keep style minimal; Draw handler only needs something editable.
178
- const latlngs = originalLayer.getLatLngs?.();
179
- if (!latlngs || !latlngs.length) {
180
- throw new Error('Geofence edit: layer has no coordinates');
184
+ if (this._draftSession.mode === 'zone') {
185
+ const serviceArea = this._draftSession.serviceArea;
186
+ this.zoneActions.modal.create(
187
+ { border: geoJson, service_area_uuid: serviceArea.id },
188
+ {
189
+ decline: (modal) => {
190
+ this.#resetDraftSession({ hideControl: true, removeDraft: true });
191
+ modal.done?.();
192
+ },
193
+ },
194
+ {
195
+ callback: (zone) => {
196
+ serviceArea?.zones?.pushObject?.(zone);
197
+ this.#resetDraftSession({ hideControl: true, removeDraft: true });
198
+ this.#showZone(zone);
199
+ },
200
+ }
201
+ );
202
+ return;
181
203
  }
182
204
 
183
- const proxy = L.polygon(latlngs, {
184
- // neutral styling so user sees edits; tweak to your theme if needed
185
- color: originalLayer.options?.color || '#3388ff',
186
- weight: 3,
187
- opacity: 0.9,
188
- fill: true,
189
- fillOpacity: originalLayer.options?.fillOpacity ?? 0.2,
190
- });
205
+ this.#openGenericLayerModal(geoJson);
206
+ }
207
+
208
+ #openGenericLayerModal(geoJson) {
209
+ this.modalsManager.show('modals/map-layer-form', {
210
+ title: 'Create new Layer',
211
+ modalClass: 'flb-resource-modal',
212
+ acceptButtonText: 'Create',
213
+ acceptButtonIcon: 'magic',
214
+ declineButtonIcon: 'times',
215
+ declineButtonIconPrefix: 'fas',
216
+ layerTypes: ['Service Area', 'Zone'],
217
+ selectedLayerType: 'Service Area',
218
+ serviceAreaTypes: this.serviceAreas.serviceAreaTypes,
219
+ layerOptions: {
220
+ trigger_on_entry: false,
221
+ trigger_on_exit: false,
222
+ dwell_threshold_minutes: null,
223
+ speed_limit_kmh: null,
224
+ description: null,
225
+ },
226
+ confirm: (modal) => {
227
+ modal.startLoading();
228
+
229
+ const selectedLayerType = modal.getOption('selectedLayerType');
230
+ const layerOptions = modal.getOption('layerOptions') ?? {};
231
+ const selectedServiceArea = layerOptions.service_area;
232
+
233
+ if (selectedLayerType === 'Zone' && !selectedServiceArea) {
234
+ this.notifications.error('Service Area required to create Zone!');
235
+ modal.stopLoading?.();
236
+ return;
237
+ }
238
+
239
+ const record = this.store.createRecord(dasherize(selectedLayerType), layerOptions);
240
+
241
+ if (selectedLayerType === 'Service Area') {
242
+ record.setProperties({
243
+ border: toMultiPolygon(geoJson),
244
+ status: 'active',
245
+ });
246
+ } else {
247
+ record.setProperties({
248
+ border: geoJson,
249
+ service_area_uuid: selectedServiceArea.id,
250
+ serviceArea: selectedServiceArea,
251
+ });
252
+ }
191
253
 
192
- // Hide original while editing so tooltips/hover don’t collide
193
- const wasVisible = !originalLayer.__hidden;
194
- this.leafletLayerVisibilityManager.hideLayer(originalLayer, { soft: false });
254
+ return record.save().then((savedRecord) => {
255
+ this.#resetDraftSession({ hideControl: true, removeDraft: true });
195
256
 
196
- // Add proxy to the edit FeatureGroup (this re-parents only the proxy)
197
- group.addLayer(proxy);
257
+ if (selectedLayerType === 'Service Area') {
258
+ this.#showServiceArea(savedRecord);
259
+ } else {
260
+ selectedServiceArea?.zones?.pushObject?.(savedRecord);
261
+ this.#showZone(savedRecord);
262
+ }
263
+
264
+ return savedRecord;
265
+ });
266
+ },
267
+ decline: (modal) => {
268
+ this.#resetDraftSession({ hideControl: true, removeDraft: true });
269
+ modal.done?.();
270
+ },
271
+ });
272
+ }
198
273
 
199
- // Optional focus
200
- if (focusBounds) {
201
- try {
202
- map.fitBounds(focusBounds, { paddingBottomRight: [0, 0], maxZoom: 16, animate: true });
203
- } catch {}
274
+ #resetDraftSession({ hideControl = false, removeDraft = false } = {}) {
275
+ if (this._draftCreatedHandler) {
276
+ this.mapManager.off('draw:created', this._draftCreatedHandler);
277
+ this._draftCreatedHandler = null;
204
278
  }
205
279
 
206
- // Enable edit mode just once for the group
207
- this.leafletMapManager.showDrawControl?.();
208
- const editHandler = draw?._toolbars?.edit?._modes?.edit?.handler;
209
- if (!editHandler) throw new Error('Geofence edit: edit handler unavailable');
210
- editHandler.enable();
211
-
212
- this._currentEdit = { original: originalLayer, proxy, wasVisible };
213
-
214
- return new Promise((resolve) => {
215
- let settled = false;
216
-
217
- const cleanup = (result) => {
218
- if (settled) return;
219
- settled = true;
220
-
221
- // Disable edit mode
222
- try {
223
- editHandler.disable();
224
- } catch {}
225
-
226
- // Remove proxy from group & map
227
- try {
228
- group.removeLayer(proxy);
229
- } catch {}
230
- try {
231
- proxy.remove();
232
- } catch {}
233
-
234
- // Restore original visibility
235
- if (wasVisible) {
236
- this.leafletLayerVisibilityManager.showLayer(originalLayer, { soft: false });
237
- }
280
+ const draftLayer = this._draftSession?.draftLayer;
281
+ if (removeDraft && draftLayer) {
282
+ this.mapManager.removeLayer(draftLayer);
283
+ }
238
284
 
239
- // Hide the toolbox again
240
- this.leafletMapManager.hideDrawControl?.();
241
-
242
- this._currentEdit = null;
243
- resolve(result);
244
- };
245
-
246
- // User pressed the ✓ in the toolbar; Leaflet.draw emits draw:edited
247
- const onEdited = (evt) => {
248
- // Apply edited coords back to original
249
- try {
250
- const edited = proxy.getLatLngs?.();
251
- if (edited?.length) {
252
- originalLayer.setLatLngs(edited);
253
- // force a redraw so ember-leaflet’s Polygon reflects new path
254
- originalLayer.redraw?.();
255
- }
256
- } catch {}
257
- cleanup({ type: 'edited', layer: originalLayer, proxy, event: evt });
258
- };
285
+ if (hideControl) {
286
+ this.mapManager.hideDrawControl();
287
+ }
259
288
 
260
- const onEditStop = () => {
261
- // If user exits edit mode without applying, treat as cancel
262
- cleanup({ type: 'cancel' });
263
- };
289
+ this._draftSession = null;
290
+ }
264
291
 
265
- map.once('draw:edited', onEdited);
266
- map.once('draw:editstop', onEditStop);
267
- });
292
+ async #editPolygonLayer(originalLayer, { focusBounds = null } = {}) {
293
+ return this.mapManager.editPolygon(originalLayer, { focusBounds });
268
294
  }
269
295
 
270
- #showZone(zone) {
271
- this.leafletLayerVisibilityManager.showModelLayer(zone);
296
+ #showZone(zone, { pin = false } = {}) {
297
+ const layer = this.mapManager.getOverlay(zone.id) ?? zone?.leafletLayer ?? null;
298
+ this.#setLayerPinned(layer, pin);
299
+ this.mapManager.showPolygon(zone.id);
272
300
  }
273
301
 
274
- #showServiceArea(serviceArea) {
302
+ #showServiceArea(serviceArea, { pin = false } = {}) {
275
303
  [serviceArea, ...(serviceArea.zones ?? [])].forEach((model) => {
276
- this.leafletLayerVisibilityManager.showModelLayer(model);
304
+ const layer = this.mapManager.getOverlay(model.id) ?? model?.leafletLayer ?? null;
305
+ this.#setLayerPinned(layer, pin);
306
+ this.mapManager.showPolygon(model.id);
277
307
  });
278
308
  }
279
309
 
280
310
  #hideServiceArea(serviceArea) {
281
311
  [serviceArea, ...(serviceArea.zones ?? [])].forEach((model) => {
282
- this.leafletLayerVisibilityManager.hideModelLayer(model);
312
+ this.#setLayerPinned(model?.leafletLayer ?? this.mapManager.getOverlay(model.id), false);
313
+ this.mapManager.hidePolygon(model.id);
283
314
  });
284
315
  }
316
+
317
+ #setLayerPinned(layer, pinned = false) {
318
+ if (!layer) return;
319
+ layer.__pinnedByFocus = pinned;
320
+ }
285
321
  }