@fleetbase/fleetops-engine 0.6.45 → 0.6.47

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 (148) 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/layout/fleet-ops-sidebar/driver-listing.js +5 -5
  10. package/addon/components/layout/fleet-ops-sidebar/fleet-listing.js +5 -5
  11. package/addon/components/layout/fleet-ops-sidebar.js +8 -0
  12. package/addon/components/maintenance-schedule/form.js +2 -1
  13. package/addon/components/map/drawer/driver-listing.js +5 -5
  14. package/addon/components/map/drawer/place-listing.js +5 -5
  15. package/addon/components/map/drawer/position-listing.js +102 -21
  16. package/addon/components/map/drawer/vehicle-listing.js +5 -5
  17. package/addon/components/map/google-live-map.hbs +9 -0
  18. package/addon/components/map/google-live-map.js +445 -0
  19. package/addon/components/map/leaflet-live-map.hbs +186 -142
  20. package/addon/components/map/leaflet-live-map.js +121 -41
  21. package/addon/components/map/toolbar/visibility-control-panel.js +2 -2
  22. package/addon/components/map/toolbar/zones-panel.hbs +9 -9
  23. package/addon/components/map/toolbar.hbs +3 -3
  24. package/addon/components/map/toolbar.js +9 -1
  25. package/addon/components/modals/map-layer-form.hbs +132 -63
  26. package/addon/components/modals/orchestrator-import.hbs +4 -11
  27. package/addon/components/modals/orchestrator-import.js +17 -1
  28. package/addon/components/modals/update-order-activity.hbs +1 -1
  29. package/addon/components/orchestrator/order-pool.js +3 -2
  30. package/addon/components/orchestrator-workbench.js +29 -41
  31. package/addon/components/order/details/detail.js +2 -2
  32. package/addon/components/order/details/payload.hbs +5 -5
  33. package/addon/components/order/details/payload.js +18 -1
  34. package/addon/components/order/details/route.hbs +2 -2
  35. package/addon/components/order/details/route.js +76 -3
  36. package/addon/components/order/form/details.hbs +1 -2
  37. package/addon/components/order/form/details.js +6 -0
  38. package/addon/components/order/form/payload.hbs +33 -0
  39. package/addon/components/order/form/payload.js +1 -0
  40. package/addon/components/order/form/route.hbs +8 -8
  41. package/addon/components/order/form/route.js +86 -18
  42. package/addon/components/order/form.js +2 -2
  43. package/addon/components/order/route-editor.hbs +209 -147
  44. package/addon/components/order/route-editor.js +143 -102
  45. package/addon/components/place/address.hbs +1 -0
  46. package/addon/components/place/address.js +12 -0
  47. package/addon/components/route-list.hbs +43 -55
  48. package/addon/components/route-list.js +47 -0
  49. package/addon/components/route-optimization-wizard-panel.js +3 -4
  50. package/addon/components/vehicle/card.hbs +4 -4
  51. package/addon/controllers/maintenance/schedules/index.js +2 -1
  52. package/addon/controllers/operations/orders/index/details.js +110 -37
  53. package/addon/controllers/operations/orders/index/new.js +1 -1
  54. package/addon/controllers/operations/orders/index.js +8 -1
  55. package/addon/controllers/settings/map.js +97 -0
  56. package/addon/controllers/settings/payments/index.js +1 -1
  57. package/addon/controllers/settings/routing.js +47 -12
  58. package/addon/extension.js +11 -0
  59. package/addon/helpers/place-address-html.js +6 -0
  60. package/addon/instance-initializers/register-google-routes.js +17 -0
  61. package/addon/instance-initializers/register-osrm.js +5 -0
  62. package/addon/routes/application.js +6 -0
  63. package/addon/routes/operations/orchestrator.js +13 -0
  64. package/addon/routes/operations/orders/index/details.js +29 -22
  65. package/addon/routes/operations/orders/index/new.js +9 -0
  66. package/addon/routes/operations/orders/index.js +0 -1
  67. package/addon/routes/settings/map.js +3 -0
  68. package/addon/routes.js +1 -0
  69. package/addon/services/entity-actions.js +1 -1
  70. package/addon/services/geofence-event-bus.js +2 -1
  71. package/addon/services/geofence.js +204 -168
  72. package/addon/services/google-routes.js +300 -0
  73. package/addon/services/leaflet-draw-restriction.js +2 -1
  74. package/addon/services/leaflet-map-manager.js +17 -0
  75. package/addon/services/map-adapter/google.js +2205 -0
  76. package/addon/services/map-adapter/leaflet.js +1042 -0
  77. package/addon/services/map-adapter-interface.js +551 -0
  78. package/addon/services/map-manager.js +768 -0
  79. package/addon/services/map-settings.js +75 -0
  80. package/addon/services/movement-tracker.js +83 -32
  81. package/addon/services/order-actions.js +11 -1
  82. package/addon/services/osrm.js +79 -6
  83. package/addon/services/position-playback.js +135 -62
  84. package/addon/services/route-engine.js +83 -0
  85. package/addon/services/service-areas.js +8 -1
  86. package/addon/styles/fleetops-engine.css +555 -0
  87. package/addon/templates/management/vehicles/index.hbs +0 -1
  88. package/addon/templates/operations/orders/index/details.hbs +0 -1
  89. package/addon/templates/settings/map.hbs +53 -0
  90. package/addon/templates/settings/routing.hbs +15 -4
  91. package/addon/utils/fleet-ops-options.js +1 -0
  92. package/addon/utils/has-valid-coordinates.js +6 -0
  93. package/addon/utils/map-provider-options.js +6 -0
  94. package/addon/utils/order-route-editing.js +86 -0
  95. package/addon/utils/order-route-summary.js +20 -0
  96. package/addon/utils/place-address-html.js +41 -0
  97. package/addon/utils/route-colors.js +43 -12
  98. package/addon/utils/route-visualization.js +93 -0
  99. package/addon/utils/vendor-integration.js +2 -1
  100. package/app/components/admin/map-settings.js +1 -0
  101. package/app/components/admin/routing-settings.js +1 -0
  102. package/app/components/cell/order-route-type.js +1 -0
  103. package/app/components/cell/place-address.js +1 -0
  104. package/app/components/map/google-live-map.js +1 -0
  105. package/app/components/place/address.js +1 -0
  106. package/app/controllers/settings/map.js +1 -0
  107. package/app/helpers/place-address-html.js +1 -0
  108. package/app/routes/settings/map.js +1 -0
  109. package/app/services/map-adapter/google.js +1 -0
  110. package/app/services/map-adapter/leaflet.js +1 -0
  111. package/app/services/map-adapter-interface.js +1 -0
  112. package/app/services/map-manager.js +1 -0
  113. package/app/services/map-settings.js +1 -0
  114. package/app/templates/settings/map.js +1 -0
  115. package/app/utils/has-valid-coordinates.js +1 -0
  116. package/app/utils/order-route-editing.js +2 -0
  117. package/app/utils/order-route-summary.js +1 -0
  118. package/app/utils/place-address-html.js +1 -0
  119. package/app/utils/route-colors.js +1 -1
  120. package/app/utils/route-visualization.js +1 -0
  121. package/assets/images/drawing-controls-spritesheet.svg +156 -0
  122. package/composer.json +1 -1
  123. package/extension.json +1 -1
  124. package/package.json +4 -4
  125. package/pnpm-workspace.yaml +7 -0
  126. package/server/src/Auth/Schemas/FleetOps.php +5 -0
  127. package/server/src/Http/Controllers/Api/v1/OrderController.php +31 -3
  128. package/server/src/Http/Controllers/Api/v1/PayloadController.php +14 -1
  129. package/server/src/Http/Controllers/Api/v1/ServiceAreaController.php +2 -0
  130. package/server/src/Http/Controllers/Api/v1/ServiceQuoteController.php +3 -2
  131. package/server/src/Http/Controllers/Api/v1/ZoneController.php +2 -0
  132. package/server/src/Http/Controllers/Internal/v1/LiveController.php +2 -1
  133. package/server/src/Http/Controllers/Internal/v1/OrchestrationController.php +7 -4
  134. package/server/src/Http/Controllers/Internal/v1/OrderController.php +90 -34
  135. package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +3 -2
  136. package/server/src/Http/Controllers/Internal/v1/SettingController.php +106 -3
  137. package/server/src/Http/Filter/OrderFilter.php +2 -1
  138. package/server/src/Http/Resources/v1/Index/Payload.php +9 -7
  139. package/server/src/Http/Resources/v1/Order.php +20 -1
  140. package/server/src/Models/Order.php +42 -1
  141. package/server/src/Models/OrderConfig.php +15 -0
  142. package/server/src/Models/Payload.php +131 -27
  143. package/server/src/Models/Place.php +182 -48
  144. package/server/src/Models/ServiceRate.php +4 -3
  145. package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +59 -23
  146. package/server/src/Support/Utils.php +36 -2
  147. package/server/src/routes.php +4 -0
  148. package/translations/en-us.yaml +1937 -1916
@@ -219,6 +219,180 @@ body.fleetbase-console .next-content-overlay > .next-content-overlay-panel-conta
219
219
  font-size: 0.85rem;
220
220
  }
221
221
 
222
+ .fleetops-route-stop-badge {
223
+ display: inline-flex;
224
+ align-items: center;
225
+ justify-content: center;
226
+ border-width: 2px;
227
+ border-style: solid;
228
+ border-color: rgb(255 255 255 / 92%);
229
+ border-radius: 9999px;
230
+ font-weight: 700;
231
+ line-height: 1;
232
+ box-shadow:
233
+ 0 1px 3px 0 rgb(0 0 0 / 15%),
234
+ 0 1px 2px -1px rgb(0 0 0 / 15%);
235
+ box-sizing: border-box;
236
+ }
237
+
238
+ .fleetops-route-editor-surface {
239
+ display: flex;
240
+ flex-direction: column;
241
+ gap: 1rem;
242
+ }
243
+
244
+ .fleetops-route-editor-section {
245
+ border: 1px solid rgb(55 65 81 / 75%);
246
+ border-radius: 0.5rem;
247
+ overflow: visible;
248
+ }
249
+
250
+ .fleetops-route-editor-section__header {
251
+ display: flex;
252
+ align-items: center;
253
+ justify-content: space-between;
254
+ gap: 1rem;
255
+ padding: 0.75rem 0.9rem;
256
+ border-bottom: 1px solid rgb(55 65 81 / 75%);
257
+ }
258
+
259
+ .fleetops-route-editor-section__title-group {
260
+ display: flex;
261
+ align-items: center;
262
+ gap: 0.65rem;
263
+ min-width: 0;
264
+ }
265
+
266
+ .fleetops-route-editor-section__title {
267
+ font-size: 0.875rem;
268
+ font-weight: 600;
269
+ color: #f3f4f6;
270
+ line-height: 1.2;
271
+ }
272
+
273
+ .fleetops-route-editor-section__subtitle {
274
+ font-size: 0.72rem;
275
+ color: #9ca3af;
276
+ margin-top: 0.12rem;
277
+ }
278
+
279
+ .fleetops-route-editor-section__body {
280
+ padding: 0.9rem;
281
+ }
282
+
283
+ .fleetops-route-editor-section__badge {
284
+ min-width: 1.9rem;
285
+ height: 1.9rem;
286
+ font-size: 0.82rem;
287
+ border-width: 2px;
288
+ border-radius: 9999px;
289
+ }
290
+
291
+ .fleetops-route-editor-stop-row {
292
+ display: grid;
293
+ grid-template-columns: 18px minmax(72px, 84px) minmax(0, 1fr) auto;
294
+ align-items: start;
295
+ gap: 0.75rem;
296
+ padding: 0.75rem;
297
+ border: 1px solid rgb(55 65 81 / 75%);
298
+ border-radius: 0.5rem;
299
+ background-color: rgb(17 24 39 / 35%);
300
+ margin-bottom: 0.25rem;
301
+ }
302
+
303
+ .fleetops-route-editor-stop-row__grip {
304
+ display: flex;
305
+ align-items: center;
306
+ justify-content: center;
307
+ color: #9ca3af;
308
+ padding-top: 0.25rem;
309
+ }
310
+
311
+ .fleetops-route-editor-stop-row__label {
312
+ display: flex;
313
+ flex-direction: row;
314
+ align-items: center;
315
+ gap: 0.5rem;
316
+ min-width: 0;
317
+ padding-top: 0.15rem;
318
+ }
319
+
320
+ .fleetops-route-editor-stop-row__badge {
321
+ min-width: 1.9rem;
322
+ height: 1.9rem;
323
+ width: 1.9rem;
324
+ font-size: 0.82rem;
325
+ flex-shrink: 0;
326
+ border-width: 2px;
327
+ border-radius: 9999px;
328
+ }
329
+
330
+ .fleetops-route-editor-stop-row__title {
331
+ font-size: 0.78rem;
332
+ font-weight: 600;
333
+ color: #e5e7eb;
334
+ line-height: 1.2;
335
+ }
336
+
337
+ .fleetops-route-editor-stop-row__field {
338
+ min-width: 0;
339
+ }
340
+
341
+ .fleetops-route-editor-stop-row__actions {
342
+ display: flex;
343
+ align-items: flex-start;
344
+ gap: 0.5rem;
345
+ }
346
+
347
+ .fleetops-route-editor-empty-state {
348
+ border: 1px dashed rgb(75 85 99 / 75%);
349
+ border-radius: 0.5rem;
350
+ padding: 0.9rem;
351
+ }
352
+
353
+ body[data-theme='light'] .fleetops-route-editor-section {
354
+ border-color: rgb(209 213 219 / 95%);
355
+ }
356
+
357
+ body[data-theme='light'] .fleetops-route-editor-section__header,
358
+ body[data-theme='light'] .fleetops-route-editor-stop-row {
359
+ border-color: rgb(209 213 219 / 85%);
360
+ }
361
+
362
+ body[data-theme='light'] .fleetops-route-editor-section__title,
363
+ body[data-theme='light'] .fleetops-route-editor-stop-row__title {
364
+ color: #111827;
365
+ }
366
+
367
+ body[data-theme='light'] .fleetops-route-editor-section__subtitle,
368
+ body[data-theme='light'] .fleetops-route-editor-empty-state .text-xs {
369
+ color: #6b7280;
370
+ }
371
+
372
+ body[data-theme='light'] .fleetops-route-editor-empty-state {
373
+ border-color: rgb(209 213 219 / 95%);
374
+ }
375
+
376
+ body[data-theme='light'] .fleetops-route-editor-stop-row {
377
+ background-color: rgb(249 250 251 / 98%);
378
+ }
379
+
380
+ @media (width <= 991px) {
381
+ .fleetops-route-editor-section__header {
382
+ flex-direction: column;
383
+ align-items: stretch;
384
+ }
385
+
386
+ .fleetops-route-editor-stop-row {
387
+ grid-template-columns: 18px minmax(64px, 78px) minmax(0, 1fr);
388
+ }
389
+
390
+ .fleetops-route-editor-stop-row__actions {
391
+ grid-column: 2 / -1;
392
+ justify-content: flex-end;
393
+ }
394
+ }
395
+
222
396
  .fleetops-pill-tabs > nav > a.ui-tab.active {
223
397
  background-color: #3b82f6;
224
398
  border: 1px solid #2563eb;
@@ -2414,6 +2588,16 @@ body[data-theme='dark'] .orchestrator-workbench,
2414
2588
  background: #111827; /* gray-900 — dark mode */
2415
2589
  }
2416
2590
 
2591
+ .orchestrator-workbench > .next-view-section-subheader {
2592
+ grid-template-columns: 1fr 5fr;
2593
+ }
2594
+
2595
+ .orchestrator-workbench > .next-view-section-subheader .next-view-section-subheader-title {
2596
+ overflow: hidden;
2597
+ text-overflow: ellipsis;
2598
+ white-space: nowrap;
2599
+ }
2600
+
2417
2601
  /* ── Toolbar ── */
2418
2602
  .workbench-toolbar {
2419
2603
  min-height: 44px;
@@ -3480,6 +3664,61 @@ body[data-theme='light'] .import-preview-table tbody tr:hover td {
3480
3664
  color: #34d399;
3481
3665
  }
3482
3666
 
3667
+ .orders-route-type-cell {
3668
+ display: inline-flex;
3669
+ align-items: center;
3670
+ overflow: visible;
3671
+ }
3672
+
3673
+ .orders-route-type-trigger {
3674
+ display: inline-flex;
3675
+ align-items: center;
3676
+ }
3677
+
3678
+ .orders-route-type-badge {
3679
+ cursor: default;
3680
+ }
3681
+
3682
+ .orders-route-type-trigger .orders-route-type-badge {
3683
+ cursor: pointer;
3684
+ }
3685
+
3686
+ .orders-route-type-preview {
3687
+ width: 20rem;
3688
+ max-width: 20rem;
3689
+ }
3690
+
3691
+ .orders-route-type-preview__header {
3692
+ display: flex;
3693
+ align-items: center;
3694
+ margin-bottom: 0.75rem;
3695
+ font-size: 0.875rem;
3696
+ font-weight: 600;
3697
+ }
3698
+
3699
+ .orders-route-type-preview__state {
3700
+ display: flex;
3701
+ align-items: center;
3702
+ color: #9ca3af;
3703
+ font-size: 0.8rem;
3704
+ }
3705
+
3706
+ .orders-route-type-preview__state--error {
3707
+ color: #fca5a5;
3708
+ }
3709
+
3710
+ .orders-route-type-preview .order-route-stop + .order-route-stop {
3711
+ margin-top: 0.5rem;
3712
+ }
3713
+
3714
+ .orders-route-type-preview__location {
3715
+ min-width: 0;
3716
+ }
3717
+
3718
+ .orders-route-type-preview .display-place-address {
3719
+ word-break: break-word;
3720
+ }
3721
+
3483
3722
  /* Row status indicator */
3484
3723
  .import-preview-status {
3485
3724
  display: inline-flex;
@@ -3509,3 +3748,319 @@ body[data-theme='light'] .import-preview-table tbody tr:hover td {
3509
3748
  color: #fbbf24;
3510
3749
  background: rgba(251, 191, 36, 6%);
3511
3750
  }
3751
+
3752
+ .fleetops-google-contextmenu {
3753
+ width: 16rem;
3754
+ background: #1f2937;
3755
+ color: #f9fafb;
3756
+ border-radius: 0.375rem;
3757
+ box-shadow: 0 10px 30px rgba(15, 23, 42, 28%);
3758
+ border: 0;
3759
+ padding: 0;
3760
+ overflow: hidden;
3761
+ }
3762
+
3763
+ .fleetops-google-contextmenu > .fleetops-google-contextmenu-item {
3764
+ display: flex;
3765
+ align-items: center;
3766
+ padding: 0.125rem 0.75rem;
3767
+ margin: 0.25rem;
3768
+ border-radius: 0.375rem;
3769
+ border: 0;
3770
+ color: #f9fafb;
3771
+ font-size: 0.75rem;
3772
+ line-height: 1.25rem;
3773
+ text-decoration: none;
3774
+ white-space: nowrap;
3775
+ overflow: hidden;
3776
+ text-overflow: ellipsis;
3777
+ }
3778
+
3779
+ .fleetops-google-contextmenu > .fleetops-google-contextmenu-item:hover {
3780
+ background: #374151;
3781
+ }
3782
+
3783
+ .fleetops-google-contextmenu > .fleetops-google-contextmenu-separator {
3784
+ height: 1px;
3785
+ margin: 0.25rem 0;
3786
+ background: #374151;
3787
+ }
3788
+
3789
+ .next-google-container-map .fleetops-google-draw {
3790
+ pointer-events: none;
3791
+ }
3792
+
3793
+ .next-google-container-map .fleetops-google-draw > .fleetops-google-draw-section {
3794
+ position: relative;
3795
+ margin-top: 0.5rem;
3796
+ }
3797
+
3798
+ .next-google-container-map .fleetops-google-draw > .fleetops-google-draw-section:first-child {
3799
+ margin-top: 0;
3800
+ }
3801
+
3802
+ .next-google-container-map .fleetops-google-draw > .fleetops-google-draw-section > .fleetops-google-draw-toolbar {
3803
+ display: flex;
3804
+ flex-direction: column;
3805
+ align-items: center;
3806
+ justify-content: center;
3807
+ gap: 0;
3808
+ padding: 0.313rem;
3809
+ border-radius: 9999px;
3810
+ background: #fff;
3811
+ box-shadow: 0 10px 30px rgba(15, 23, 42, 18%);
3812
+ border: 0;
3813
+ pointer-events: auto;
3814
+ }
3815
+
3816
+ .next-google-container-map .fleetops-google-draw > .fleetops-google-draw-section > .fleetops-google-draw-toolbar > a {
3817
+ display: flex;
3818
+ align-items: center;
3819
+ justify-content: center;
3820
+ width: 30px;
3821
+ height: 30px;
3822
+ margin: 0 auto 0.5rem;
3823
+ border-radius: 9999px;
3824
+ color: #111827;
3825
+ text-decoration: none;
3826
+ font-size: 0;
3827
+ line-height: 1;
3828
+ background-image: linear-gradient(transparent, transparent), url('/engines-dist/images/drawing-controls-spritesheet.svg');
3829
+ background-repeat: no-repeat;
3830
+ background-size: 300px 30px;
3831
+ background-clip: padding-box;
3832
+ background-origin: content-box;
3833
+ }
3834
+
3835
+ .next-google-container-map .fleetops-google-draw > .fleetops-google-draw-section > .fleetops-google-draw-toolbar > a:hover {
3836
+ background-color: #e5e7eb;
3837
+ }
3838
+
3839
+ .next-google-container-map .fleetops-google-draw > .fleetops-google-draw-section > .fleetops-google-draw-toolbar > a:last-child {
3840
+ margin-bottom: 0;
3841
+ }
3842
+
3843
+ .next-google-container-map .fleetops-google-draw-toolbar .fleetops-google-draw-draw-polygon {
3844
+ background-position: -31px -2px;
3845
+ }
3846
+
3847
+ .next-google-container-map .fleetops-google-draw-toolbar .fleetops-google-draw-draw-rectangle {
3848
+ background-position: -62px -2px;
3849
+ }
3850
+
3851
+ .next-google-container-map .fleetops-google-draw-toolbar .fleetops-google-draw-draw-circle {
3852
+ background-position: -92px -2px;
3853
+ }
3854
+
3855
+ .next-google-container-map .fleetops-google-draw-toolbar .fleetops-google-draw-edit-edit {
3856
+ background-position: -152px -2px;
3857
+ }
3858
+
3859
+ .next-google-container-map .fleetops-google-draw-toolbar .fleetops-google-draw-edit-remove {
3860
+ background-position: -182px -2px;
3861
+ }
3862
+
3863
+ .next-google-container-map .fleetops-google-draw-actions {
3864
+ list-style: none;
3865
+ margin: 0;
3866
+ padding: 0;
3867
+ }
3868
+
3869
+ .next-google-container-map .fleetops-google-draw-actions li {
3870
+ display: inline-flex;
3871
+ }
3872
+
3873
+ .next-google-container-map .fleetops-google-draw-actions li .fleetops-google-draw-action-link {
3874
+ display: inline-flex;
3875
+ align-items: center;
3876
+ justify-content: center;
3877
+ height: 1.75rem;
3878
+ padding: 0 0.75rem;
3879
+ background: #fff;
3880
+ border: 1px solid #e5e7eb;
3881
+ color: #111827;
3882
+ text-decoration: none;
3883
+ font-size: 0.75rem;
3884
+ line-height: 1.75rem;
3885
+ }
3886
+
3887
+ .next-google-container-map .fleetops-google-draw-actions li:first-child .fleetops-google-draw-action-link {
3888
+ border-radius: 0.375rem 0 0 0.375rem;
3889
+ }
3890
+
3891
+ .next-google-container-map .fleetops-google-draw-actions li:last-child .fleetops-google-draw-action-link {
3892
+ border-radius: 0 0.375rem 0.375rem 0;
3893
+ }
3894
+
3895
+ .next-google-container-map .fleetops-google-draw-actions li .fleetops-google-draw-action-link:hover {
3896
+ background: #f3f4f6;
3897
+ }
3898
+
3899
+ body[data-theme='light'] .fleetops-google-contextmenu {
3900
+ background: #fff;
3901
+ color: #111827;
3902
+ box-shadow: 0 10px 30px rgba(15, 23, 42, 18%);
3903
+ }
3904
+
3905
+ body[data-theme='light'] .fleetops-google-contextmenu > .fleetops-google-contextmenu-separator {
3906
+ background: #e5e7eb;
3907
+ }
3908
+
3909
+ body[data-theme='light'] .fleetops-google-contextmenu > .fleetops-google-contextmenu-item {
3910
+ color: #111827;
3911
+ }
3912
+
3913
+ body[data-theme='light'] .fleetops-google-contextmenu > .fleetops-google-contextmenu-item:hover {
3914
+ background: #e5e7eb;
3915
+ }
3916
+
3917
+ body[data-theme='dark'] .next-google-container-map .fleetops-google-draw > .fleetops-google-draw-section > .fleetops-google-draw-toolbar {
3918
+ background: #111827;
3919
+ }
3920
+
3921
+ body[data-theme='dark'] .next-google-container-map .fleetops-google-draw > .fleetops-google-draw-section > .fleetops-google-draw-toolbar > a {
3922
+ color: #f9fafb;
3923
+ background-image: linear-gradient(transparent, transparent), url('/engines-dist/images/drawing-controls-spritesheet-inverted.png');
3924
+ background-size: 300px 30px;
3925
+ }
3926
+
3927
+ body[data-theme='dark'] .next-google-container-map .fleetops-google-draw > .fleetops-google-draw-section > .fleetops-google-draw-toolbar > a:hover {
3928
+ background-color: #374151;
3929
+ }
3930
+
3931
+ body[data-theme='dark'] .next-google-container-map .fleetops-google-draw-actions li .fleetops-google-draw-action-link {
3932
+ background: #111827;
3933
+ border-color: #374151;
3934
+ color: #f9fafb;
3935
+ }
3936
+
3937
+ body[data-theme='dark'] .next-google-container-map .fleetops-google-draw-actions li .fleetops-google-draw-action-link:hover {
3938
+ background: #1f2937;
3939
+ }
3940
+
3941
+ .fleetops-google-polygon-label {
3942
+ display: inline-flex;
3943
+ align-items: center;
3944
+ justify-content: center;
3945
+ max-width: 180px;
3946
+ padding: 2px 8px;
3947
+ border-radius: 9999px;
3948
+ background: rgba(17, 24, 39, 88%);
3949
+ color: #f9fafb;
3950
+ font-size: 11px;
3951
+ font-weight: 600;
3952
+ line-height: 1.25;
3953
+ white-space: nowrap;
3954
+ text-overflow: ellipsis;
3955
+ overflow: hidden;
3956
+ box-shadow: 0 6px 16px rgba(15, 23, 42, 22%);
3957
+ }
3958
+
3959
+ .fleetops-google-hover-tooltip {
3960
+ position: absolute;
3961
+ z-index: 99999;
3962
+ display: none;
3963
+ max-width: 240px;
3964
+ padding: 0.45rem 0.65rem;
3965
+ border-radius: 0.625rem;
3966
+ background: rgba(17, 24, 39, 94%);
3967
+ color: #f9fafb;
3968
+ font-size: 0.75rem;
3969
+ line-height: 1.25;
3970
+ pointer-events: none;
3971
+ box-shadow: 0 10px 30px rgba(15, 23, 42, 22%);
3972
+ transform: translateY(-50%);
3973
+ }
3974
+
3975
+ .fleetops-google-hover-tooltip__title {
3976
+ font-weight: 700;
3977
+ white-space: nowrap;
3978
+ }
3979
+
3980
+ .fleetops-google-hover-tooltip__meta {
3981
+ margin-top: 0.125rem;
3982
+ font-size: 0.7rem;
3983
+ color: #d1d5db;
3984
+ }
3985
+
3986
+ .fleetops-position-history-dot {
3987
+ width: 8px;
3988
+ height: 8px;
3989
+ border-radius: 9999px;
3990
+ background: #3b82f6;
3991
+ border: 1px solid rgba(255, 255, 255, 95%);
3992
+ box-shadow: 0 0 0 1px rgba(59, 130, 246, 25%);
3993
+ }
3994
+
3995
+ .fleetops-google-popover {
3996
+ min-width: 220px;
3997
+ max-width: 260px;
3998
+ padding: 0.75rem;
3999
+ border-radius: 1rem;
4000
+ background: #111827;
4001
+ color: #f9fafb;
4002
+ box-shadow: 0 16px 40px rgba(15, 23, 42, 28%);
4003
+ }
4004
+
4005
+ .fleetops-google-popover--compact {
4006
+ min-width: 180px;
4007
+ }
4008
+
4009
+ .fleetops-google-popover__row {
4010
+ display: flex;
4011
+ gap: 0.75rem;
4012
+ align-items: flex-start;
4013
+ }
4014
+
4015
+ .fleetops-google-popover__avatar {
4016
+ width: 48px;
4017
+ height: 48px;
4018
+ flex-shrink: 0;
4019
+ object-fit: cover;
4020
+ border-radius: 0.75rem;
4021
+ background: #374151;
4022
+ }
4023
+
4024
+ .fleetops-google-popover__vehicle {
4025
+ width: 56px;
4026
+ height: 48px;
4027
+ flex-shrink: 0;
4028
+ object-fit: cover;
4029
+ border-radius: 0.75rem;
4030
+ background: #374151;
4031
+ }
4032
+
4033
+ .fleetops-google-popover__body {
4034
+ min-width: 0;
4035
+ flex: 1;
4036
+ }
4037
+
4038
+ .fleetops-google-popover__title {
4039
+ font-size: 0.8rem;
4040
+ line-height: 1.2;
4041
+ font-weight: 700;
4042
+ color: #f9fafb;
4043
+ }
4044
+
4045
+ .fleetops-google-popover__meta {
4046
+ margin-top: 0.2rem;
4047
+ font-size: 0.72rem;
4048
+ line-height: 1.35;
4049
+ color: #d1d5db;
4050
+ }
4051
+
4052
+ .gm-style .gm-style-iw-c {
4053
+ padding: 0 !important;
4054
+ background: transparent !important;
4055
+ border-radius: 1rem !important;
4056
+ box-shadow: none !important;
4057
+ }
4058
+
4059
+ .gm-style .gm-style-iw-d {
4060
+ overflow: hidden !important;
4061
+ max-height: none !important;
4062
+ }
4063
+
4064
+ .gm-style .gm-style-iw-tc::after {
4065
+ background: #111827 !important;
4066
+ }
@@ -33,7 +33,6 @@
33
33
  <Layout::Resource::CardsGrid
34
34
  @resource="vehicle"
35
35
  @data={{@model}}
36
- @gap="sm"
37
36
  @columns="4"
38
37
  @showPagination={{true}}
39
38
  @page={{this.page}}
@@ -8,7 +8,6 @@
8
8
  @headerClass="no-bottom-border"
9
9
  @bodyClass="no-scroll"
10
10
  @width="530px"
11
- @onOpen={{this.setup}}
12
11
  >
13
12
  <TabNavigation @tabs={{this.tabs}} @contentClass="scrollable" @tablistClass="pl-2">
14
13
  {{outlet}}
@@ -0,0 +1,53 @@
1
+ <Layout::Section::Header @title={{t "settings.map.fleet-ops-map-settings"}}>
2
+ <Button
3
+ @type="primary"
4
+ @size="sm"
5
+ @icon="save"
6
+ @text={{t "common.save-changes"}}
7
+ @onClick={{perform this.saveSettings}}
8
+ @disabled={{this.saveSettings.isRunning}}
9
+ @isLoading={{or this.saveSettings.isRunning this.getSettings.isRunning}}
10
+ @permission="fleet-ops update map-settings"
11
+ />
12
+ </Layout::Section::Header>
13
+ <Layout::Section::Body class="overflow-y-scroll h-full">
14
+ <div class="container mx-auto h-screen">
15
+ <div class="max-w-3xl my-10 mx-auto space-y-6">
16
+
17
+ {{!-- ─── Map Provider Panel ──────────────────────────────────────────── --}}
18
+ <ContentPanel @title={{t "settings.map.configure-map"}} @open={{true}} @wrapperClass="bordered-classic">
19
+ {{#if this.settingsLoaded}}
20
+
21
+ {{!-- Provider selector --}}
22
+ <InputGroup
23
+ @name={{t "settings.map.map-provider"}}
24
+ @helpText={{t "settings.map.map-provider-help-text"}}
25
+ >
26
+ <Select
27
+ class="w-full flex-1"
28
+ @value={{this.mapProvider}}
29
+ @options={{this.mapProviderOptions}}
30
+ @optionLabel="label"
31
+ @optionValue="value"
32
+ @placeholder={{t "settings.map.select-map-provider"}}
33
+ @onSelect={{this.selectMapProvider}}
34
+ />
35
+ </InputGroup>
36
+
37
+ {{else}}
38
+ {{!-- Loading state --}}
39
+ <div class="flex items-center justify-center py-8">
40
+ <Spinner />
41
+ </div>
42
+ {{/if}}
43
+ </ContentPanel>
44
+
45
+ {{!-- Extension point for third-party panels --}}
46
+ <RegistryYield @registry="fleet-ops:template:settings:map" as |RegistryComponent|>
47
+ <RegistryComponent @controller={{this}} />
48
+ </RegistryYield>
49
+
50
+ </div>
51
+ </div>
52
+ <Spacer @height="600px" />
53
+ </Layout::Section::Body>
@@ -16,15 +16,26 @@
16
16
  <ContentPanel @title={{t "settings.routing.configure-routing"}} @open={{true}} @wrapperClass="bordered-classic">
17
17
  <InputGroup @name={{t "settings.routing.routing-service"}} @helpText={{t "settings.routing.routing-service-help-text"}}>
18
18
  <Select
19
- @value={{this.routerService}}
20
- @options={{this.leafletRoutingControl.availableServices}}
19
+ @value={{this.displayEngine}}
20
+ @options={{this.routeEngine.availableServices}}
21
21
  @optionLabel="name"
22
22
  @optionValue="key"
23
- @onSelect={{fn (mut this.routerService)}}
23
+ @onSelect={{fn (mut this.displayEngine)}}
24
24
  @placeholder={{t "settings.routing.select-routing-service"}}
25
25
  class="w-full"
26
26
  />
27
27
  </InputGroup>
28
+ <InputGroup @name="Routing Optimization Engine" @helpText="Choose which engine should optimize stop order before the route is rendered.">
29
+ <Select
30
+ @value={{this.optimizationEngine}}
31
+ @options={{this.routeOptimization.availableServices}}
32
+ @optionLabel="name"
33
+ @optionValue="key"
34
+ @onSelect={{fn (mut this.optimizationEngine)}}
35
+ @placeholder="Select route optimization engine"
36
+ class="w-full"
37
+ />
38
+ </InputGroup>
28
39
  <InputGroup @name={{t "settings.routing.routing-distance-unit"}} @helpText={{t "settings.routing.routing-distance-unit-help-text"}}>
29
40
  <Select
30
41
  @value={{this.routingUnit}}
@@ -43,4 +54,4 @@
43
54
  </div>
44
55
  </div>
45
56
  <Spacer @height="600px" />
46
- </Layout::Section::Body>
57
+ </Layout::Section::Body>
@@ -124,6 +124,7 @@ export const contactTypes = [
124
124
  ];
125
125
 
126
126
  export const entityTypes = [
127
+ { label: 'Entity', value: 'entity', description: 'Standard generic entity representing an item.' },
127
128
  { label: 'Parcel', value: 'parcel', description: 'Standard parcel or package used by parcel-based pricing.' },
128
129
  { label: 'Package', value: 'package', description: 'Generic package or boxed item.' },
129
130
  { label: 'Product', value: 'product', description: 'Sellable product or catalog item.' },
@@ -0,0 +1,6 @@
1
+ export default function hasValidCoordinates(place) {
2
+ const latitude = Number(place?.latitude);
3
+ const longitude = Number(place?.longitude);
4
+
5
+ return Number.isFinite(latitude) && Number.isFinite(longitude) && latitude !== 0 && longitude !== 0;
6
+ }
@@ -0,0 +1,6 @@
1
+ const MAP_PROVIDER_OPTIONS = [
2
+ { label: 'Leaflet', value: 'leaflet' },
3
+ { label: 'Google Maps', value: 'google' },
4
+ ];
5
+
6
+ export default MAP_PROVIDER_OPTIONS;