@fleetbase/fleetops-engine 0.6.49 → 0.6.51

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 (271) hide show
  1. package/README.md +185 -67
  2. package/addon/components/contact/panel-header.hbs +11 -48
  3. package/addon/components/customer/create-order-form.hbs +2 -2
  4. package/addon/components/customer/details.hbs +1 -2
  5. package/addon/components/customer/form.hbs +62 -50
  6. package/addon/components/customer/form.js +27 -1
  7. package/addon/components/customer/order-form.hbs +2 -2
  8. package/addon/components/customer/orders.hbs +50 -2
  9. package/addon/components/customer/orders.js +67 -5
  10. package/addon/components/display-place.hbs +2 -2
  11. package/addon/components/driver/form.hbs +1 -1
  12. package/addon/components/home/getting-started-guidance.hbs +113 -0
  13. package/addon/components/home/getting-started-guidance.js +107 -0
  14. package/addon/components/issue/details/comments.hbs +3 -0
  15. package/addon/components/issue/details/comments.js +3 -0
  16. package/addon/components/issue/details/correspondence.hbs +9 -0
  17. package/addon/components/issue/details/correspondence.js +83 -0
  18. package/addon/components/issue/details/documents.hbs +69 -0
  19. package/addon/components/issue/details/documents.js +85 -0
  20. package/addon/components/issue/details.hbs +220 -59
  21. package/addon/components/issue/details.js +245 -0
  22. package/addon/components/issue/form.hbs +19 -1
  23. package/addon/components/issue/panel-header.hbs +43 -0
  24. package/addon/components/issue/panel-header.js +22 -0
  25. package/addon/components/issue/timeline.hbs +40 -0
  26. package/addon/components/issue/timeline.js +45 -0
  27. package/addon/components/leaflet-tracking-marker.js +3 -0
  28. package/addon/components/map/drawer/position-listing.js +3 -0
  29. package/addon/components/map/google-live-map.js +6 -2
  30. package/addon/components/map/leaflet-live-map.hbs +48 -14
  31. package/addon/components/map/leaflet-live-map.js +201 -20
  32. package/addon/components/map/order-list-overlay.hbs +4 -4
  33. package/addon/components/modals/convert-customer-to-vendor.hbs +30 -0
  34. package/addon/components/modals/convert-customer-to-vendor.js +55 -0
  35. package/addon/components/modals/issue-assign.hbs +20 -0
  36. package/addon/components/modals/issue-assign.js +49 -0
  37. package/addon/components/modals/issue-close.hbs +30 -0
  38. package/addon/components/modals/issue-close.js +73 -0
  39. package/addon/components/modals/issue-status.hbs +20 -0
  40. package/addon/components/modals/issue-status.js +43 -0
  41. package/addon/components/modals/order-change-destination.hbs +46 -0
  42. package/addon/components/modals/order-change-destination.js +106 -0
  43. package/addon/components/modals/reset-customer-credentials.js +12 -6
  44. package/addon/components/modals/stop-activity.hbs +28 -0
  45. package/addon/components/modals/stop-activity.js +52 -0
  46. package/addon/components/modals/update-order-activity.hbs +78 -2
  47. package/addon/components/modals/update-order-activity.js +197 -5
  48. package/addon/components/orchestrator/resource-panel.hbs +0 -10
  49. package/addon/components/orchestrator/resource-panel.js +2 -4
  50. package/addon/components/order/activity-list.hbs +2 -2
  51. package/addon/components/order/activity-timeline.hbs +31 -29
  52. package/addon/components/order/details/activity.hbs +3 -3
  53. package/addon/components/order/details/activity.js +13 -2
  54. package/addon/components/order/details/documents.js +2 -0
  55. package/addon/components/order/details/proof.hbs +40 -38
  56. package/addon/components/order/details/proof.js +31 -4
  57. package/addon/components/order/details/purchase-rate.hbs +2 -2
  58. package/addon/components/order/details/route.hbs +11 -3
  59. package/addon/components/order/details/route.js +91 -14
  60. package/addon/components/order/details/tracking.hbs +101 -72
  61. package/addon/components/order/details/tracking.js +149 -2
  62. package/addon/components/order/details.hbs +5 -5
  63. package/addon/components/order/details.js +19 -1
  64. package/addon/components/order/form/details.hbs +3 -3
  65. package/addon/components/order/form/documents.js +11 -4
  66. package/addon/components/order/form/route.hbs +108 -104
  67. package/addon/components/order/form/route.js +37 -1
  68. package/addon/components/order/form/service-rate.hbs +2 -2
  69. package/addon/components/order/header.hbs +1 -1
  70. package/addon/components/order-config-manager/activity-flow.hbs +4 -1
  71. package/addon/components/order-config-manager/activity-flow.js +178 -12
  72. package/addon/components/place/address.js +1 -1
  73. package/addon/components/route-list.hbs +17 -29
  74. package/addon/components/route-list.js +42 -10
  75. package/addon/components/service-rate/details.hbs +42 -1
  76. package/addon/components/service-rate/form.hbs +119 -1
  77. package/addon/components/service-rate/form.js +59 -0
  78. package/addon/components/vehicle/form.js +1 -1
  79. package/addon/components/vehicle/pill.hbs +1 -1
  80. package/addon/components/vendor/personnel-manager.hbs +166 -0
  81. package/addon/components/vendor/personnel-manager.js +143 -0
  82. package/addon/components/widget/live-fleet.hbs +1 -0
  83. package/addon/components/widget/live-fleet.js +2 -7
  84. package/addon/components/widget/revenue-trend.hbs +1 -1
  85. package/addon/components/widget/revenue-trend.js +75 -2
  86. package/addon/controllers/management/contacts/customers/details.js +31 -13
  87. package/addon/controllers/management/contacts/customers.js +16 -5
  88. package/addon/controllers/management/contacts/index/details.js +31 -13
  89. package/addon/controllers/management/contacts/index.js +7 -0
  90. package/addon/controllers/management/issues/index/details.js +52 -6
  91. package/addon/controllers/management/vehicles/index/new.js +1 -1
  92. package/addon/controllers/management/vendors/index/details.js +4 -0
  93. package/addon/controllers/operations/orders/index/details.js +15 -7
  94. package/addon/controllers/operations/orders/index.js +15 -1
  95. package/addon/extension.js +19 -12
  96. package/addon/helpers/format-duration.js +1 -38
  97. package/addon/helpers/place-address-html.js +2 -2
  98. package/addon/initializers/load-leaflet-assets.js +5 -6
  99. package/addon/initializers/patch-ember-leaflet-tooltip-layer.js +51 -0
  100. package/addon/routes/management/issues/index/details.js +1 -1
  101. package/addon/routes/operations/service-rates/index.js +1 -1
  102. package/addon/routes.js +1 -0
  103. package/addon/services/contact-actions.js +231 -1
  104. package/addon/services/geofence.js +163 -19
  105. package/addon/services/getting-started.js +44 -0
  106. package/addon/services/issue-actions.js +71 -0
  107. package/addon/services/leaflet-contextmenu-manager.js +27 -0
  108. package/addon/services/leaflet-map-manager.js +2 -0
  109. package/addon/services/map-adapter/leaflet.js +19 -0
  110. package/addon/services/service-areas.js +3 -0
  111. package/addon/services/vehicle-actions.js +1 -1
  112. package/addon/styles/fleetops-engine.css +1127 -1
  113. package/addon/templates/management/contacts/customers/details.hbs +1 -1
  114. package/addon/templates/management/contacts/index/details.hbs +1 -1
  115. package/addon/templates/management/contacts.hbs +2 -2
  116. package/addon/templates/management/issues/index/details/index.hbs +3 -1
  117. package/addon/templates/management/issues/index/details.hbs +3 -2
  118. package/addon/templates/management/vendors/index/details/personnel.hbs +1 -2
  119. package/addon/templates/operations/orders/index/details/index.hbs +2 -2
  120. package/addon/templates/operations/orders/index.hbs +1 -1
  121. package/addon/templates/operations/service-rates/index/edit.hbs +1 -0
  122. package/addon/templates/operations/service-rates/index/new.hbs +1 -0
  123. package/addon/templates/settings/payments/onboard.hbs +8 -8
  124. package/addon/utils/fleet-ops-options.js +2 -1
  125. package/addon/utils/leaflet-draw-namespace-guard.js +22 -0
  126. package/addon/utils/leaflet-plugin-loader.js +219 -0
  127. package/addon/utils/normalize-order-config-flow.js +41 -0
  128. package/addon/utils/place-address-html.js +3 -2
  129. package/addon/utils/route-visualization.js +54 -7
  130. package/addon/utils/setup-customer-portal.js +4 -64
  131. package/app/components/home/getting-started-guidance.js +1 -0
  132. package/app/components/issue/details/comments.js +1 -0
  133. package/app/components/issue/details/correspondence.js +1 -0
  134. package/app/components/issue/details/documents.js +1 -0
  135. package/app/components/issue/panel-header.js +1 -0
  136. package/app/components/modals/convert-customer-to-vendor.js +1 -0
  137. package/app/components/modals/issue-assign.js +1 -0
  138. package/app/components/modals/issue-close.js +1 -0
  139. package/app/components/modals/issue-status.js +1 -0
  140. package/app/components/modals/order-change-destination.js +1 -0
  141. package/app/components/modals/stop-activity.js +1 -0
  142. package/app/components/vendor/personnel-manager.js +1 -0
  143. package/app/helpers/format-duration.js +1 -1
  144. package/app/services/getting-started.js +1 -0
  145. package/app/utils/leaflet-draw-namespace-guard.js +1 -0
  146. package/app/utils/leaflet-plugin-loader.js +1 -0
  147. package/composer.json +1 -1
  148. package/extension.json +1 -1
  149. package/package.json +5 -5
  150. package/server/migrations/2026_05_21_000001_add_geographic_rules_to_service_rate_fees_table.php +37 -0
  151. package/server/migrations/2026_05_25_000001_add_endpoint_tracking_refs_to_payloads_table.php +50 -0
  152. package/server/migrations/2026_05_27_000001_default_driver_vehicle_statuses_to_available.php +48 -0
  153. package/server/migrations/2026_05_31_000001_add_portal_fields_to_vendor_personnels_table.php +30 -0
  154. package/server/migrations/2026_06_03_000000_ensure_tags_column_on_issues_table.php +31 -0
  155. package/server/migrations/2026_06_03_000001_add_order_uuid_to_issues_table.php +25 -0
  156. package/server/migrations/2026_06_04_000001_backfill_order_transaction_subjects.php +39 -0
  157. package/server/migrations/2026_06_04_000002_backfill_order_transaction_directions.php +24 -0
  158. package/server/resources/views/labels/default.php +57 -49
  159. package/server/resources/views/labels/waypoint-label.php +27 -20
  160. package/server/resources/views/mail/customer-credentials.blade.php +16 -13
  161. package/server/seeders/Testing/ConnectivitySeeder.php +1 -0
  162. package/server/seeders/Testing/FleetSeeder.php +22 -22
  163. package/server/seeders/Testing/OrdersSeeder.php +6 -22
  164. package/server/seeders/Testing/VroomOrchestrationSeeder.php +21 -21
  165. package/server/src/Console/Commands/AuditCustomerUserConflicts.php +75 -0
  166. package/server/src/Exceptions/CustomerUserConflictException.php +13 -0
  167. package/server/src/Expansions/UserFilterExpansion.php +28 -0
  168. package/server/src/Http/Controllers/Api/v1/ContactController.php +17 -2
  169. package/server/src/Http/Controllers/Api/v1/CustomerController.php +876 -0
  170. package/server/src/Http/Controllers/Api/v1/DriverController.php +1 -1
  171. package/server/src/Http/Controllers/Api/v1/IssueController.php +2 -0
  172. package/server/src/Http/Controllers/Api/v1/OrderConfigController.php +54 -0
  173. package/server/src/Http/Controllers/Api/v1/OrderController.php +109 -80
  174. package/server/src/Http/Controllers/Api/v1/PlaceController.php +10 -58
  175. package/server/src/Http/Controllers/Api/v1/ServiceAreaController.php +2 -2
  176. package/server/src/Http/Controllers/Api/v1/VehicleController.php +20 -3
  177. package/server/src/Http/Controllers/Internal/v1/ContactController.php +154 -0
  178. package/server/src/Http/Controllers/Internal/v1/CustomerController.php +116 -6
  179. package/server/src/Http/Controllers/Internal/v1/GettingStartedController.php +17 -0
  180. package/server/src/Http/Controllers/Internal/v1/IssueController.php +251 -0
  181. package/server/src/Http/Controllers/Internal/v1/LiveController.php +101 -66
  182. package/server/src/Http/Controllers/Internal/v1/OrderController.php +530 -52
  183. package/server/src/Http/Controllers/Internal/v1/PlaceController.php +10 -68
  184. package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +1 -1
  185. package/server/src/Http/Controllers/Internal/v1/SettingController.php +2 -2
  186. package/server/src/Http/Controllers/Internal/v1/VehicleController.php +54 -1
  187. package/server/src/Http/Controllers/Internal/v1/VendorController.php +89 -0
  188. package/server/src/Http/Middleware/AuthenticateCustomerToken.php +34 -0
  189. package/server/src/Http/Requests/CreateCustomerOrderRequest.php +63 -0
  190. package/server/src/Http/Requests/CreateCustomerRequest.php +48 -0
  191. package/server/src/Http/Requests/CreateDriverRequest.php +1 -1
  192. package/server/src/Http/Requests/CreateIssueRequest.php +4 -0
  193. package/server/src/Http/Requests/CreateVehicleRequest.php +25 -1
  194. package/server/src/Http/Requests/Internal/CreateDriverRequest.php +1 -1
  195. package/server/src/Http/Requests/UpdateIssueRequest.php +4 -0
  196. package/server/src/Http/Requests/VerifyCreateCustomerRequest.php +29 -0
  197. package/server/src/Http/Resources/Internal/v1/OrderConfig.php +41 -0
  198. package/server/src/Http/Resources/v1/Customer.php +79 -0
  199. package/server/src/Http/Resources/v1/Index/Order.php +12 -9
  200. package/server/src/Http/Resources/v1/Issue.php +25 -0
  201. package/server/src/Http/Resources/v1/Order.php +20 -18
  202. package/server/src/Http/Resources/v1/OrderConfig.php +74 -0
  203. package/server/src/Http/Resources/v1/Payload.php +23 -20
  204. package/server/src/Http/Resources/v1/ServiceArea.php +33 -19
  205. package/server/src/Http/Resources/v1/ServiceRateFee.php +7 -0
  206. package/server/src/Listeners/HandleOrderDispatched.php +2 -2
  207. package/server/src/Mail/CustomerCredentialsMail.php +5 -6
  208. package/server/src/Models/Contact.php +122 -66
  209. package/server/src/Models/Customer.php +64 -0
  210. package/server/src/Models/Driver.php +12 -3
  211. package/server/src/Models/Entity.php +2 -2
  212. package/server/src/Models/Issue.php +47 -6
  213. package/server/src/Models/Order.php +62 -10
  214. package/server/src/Models/Payload.php +17 -1
  215. package/server/src/Models/ServiceArea.php +1 -1
  216. package/server/src/Models/ServiceRate.php +273 -1
  217. package/server/src/Models/ServiceRateFee.php +39 -9
  218. package/server/src/Models/TrackingNumber.php +8 -2
  219. package/server/src/Models/Vehicle.php +18 -2
  220. package/server/src/Models/VendorPersonnel.php +10 -1
  221. package/server/src/Models/Waypoint.php +2 -2
  222. package/server/src/Models/Zone.php +1 -1
  223. package/server/src/Notifications/LateDeparture.php +2 -2
  224. package/server/src/Notifications/ProlongedStoppage.php +2 -2
  225. package/server/src/Notifications/RouteDeviation.php +2 -2
  226. package/server/src/Observers/ContactObserver.php +2 -0
  227. package/server/src/Observers/PurchaseRateObserver.php +20 -0
  228. package/server/src/Observers/ServiceRateObserver.php +6 -6
  229. package/server/src/Observers/ZoneObserver.php +56 -0
  230. package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +1 -1
  231. package/server/src/Providers/FleetOpsServiceProvider.php +2 -0
  232. package/server/src/Support/Analytics/AbstractAnalytics.php +6 -6
  233. package/server/src/Support/Analytics/FuelEfficiency.php +2 -2
  234. package/server/src/Support/Analytics/OrdersByStatus.php +1 -1
  235. package/server/src/Support/Analytics/RevenueTrend.php +0 -1
  236. package/server/src/Support/Analytics/TopDrivers.php +10 -2
  237. package/server/src/Support/CustomerAuth.php +98 -0
  238. package/server/src/Support/GettingStarted.php +144 -0
  239. package/server/src/Support/LabelPdf.php +52 -0
  240. package/server/src/Support/Metrics/AbstractMetric.php +4 -4
  241. package/server/src/Support/PlaceSearch.php +282 -0
  242. package/server/src/Support/ResolvesOrderServiceStops.php +522 -0
  243. package/server/src/Tracking/TrackingContextBuilder.php +41 -38
  244. package/server/src/Tracking/TrackingIntelligenceService.php +109 -9
  245. package/server/src/Tracking/TrackingStop.php +13 -11
  246. package/server/src/routes.php +76 -10
  247. package/server/tests/AnalyticsRoutesTest.php +8 -0
  248. package/server/tests/CapacityAllocationEngineTest.php +10 -10
  249. package/server/tests/CustomerContactIdentitySafetyTest.php +95 -0
  250. package/server/tests/CustomerEndpointTest.php +199 -0
  251. package/server/tests/DriverVehicleStatusDefaultTest.php +39 -0
  252. package/server/tests/GettingStartedStatusTest.php +21 -0
  253. package/server/tests/LabelPdfRenderingTest.php +23 -0
  254. package/server/tests/LiveControllerViewportTest.php +60 -0
  255. package/server/tests/MetricsRegistryTest.php +4 -8
  256. package/server/tests/OrchestrationPayloadBuilderTest.php +2 -2
  257. package/server/tests/OrderActivityFlowRegressionTest.php +116 -0
  258. package/server/tests/OrderConfigEndpointTest.php +86 -0
  259. package/server/tests/OrderFileAttachmentTest.php +20 -0
  260. package/server/tests/ServiceAreaZoneApiShapeTest.php +78 -0
  261. package/server/tests/VehicleDriverAssignmentTest.php +18 -0
  262. package/translations/ar-ae.yml +9 -0
  263. package/translations/bg-bg.yaml +9 -0
  264. package/translations/en-us.yaml +12 -0
  265. package/translations/es-pa.yaml +9 -0
  266. package/translations/fr-fr.yaml +9 -0
  267. package/translations/mn-mn.yaml +9 -0
  268. package/translations/pt-br.yaml +9 -0
  269. package/translations/ru-ru.yaml +9 -0
  270. package/translations/vi-vn.yaml +9 -0
  271. package/server/migrations/2026_05_13_000001_repair_customer_contact_user_types.php +0 -86
@@ -12,6 +12,7 @@ import generateUUID from '@fleetbase/ember-core/utils/generate-uuid';
12
12
  import inlineTask from '@fleetbase/ember-core/utils/inline-task';
13
13
  import createFlowActivity from '../../utils/create-flow-activity';
14
14
  import contextComponentCallback from '@fleetbase/ember-core/utils/context-component-callback';
15
+ import normalizeOrderConfigFlow, { getOrderConfigFlowRootCode } from '../../utils/normalize-order-config-flow';
15
16
 
16
17
  /**
17
18
  * Manages the activity flow for order configuration, allowing users to create, edit, and view a sequence of activities.
@@ -53,6 +54,13 @@ export default class OrderConfigManagerActivityFlowComponent extends Component {
53
54
  */
54
55
  @tracked graph;
55
56
 
57
+ /**
58
+ * True when the rendered graph is wider than the visible Activity Flow area.
59
+ *
60
+ * @type {Boolean}
61
+ */
62
+ @tracked hasHorizontalOverflow = false;
63
+
56
64
  /**
57
65
  * Checks if listener pointerdown listener is called.
58
66
  *
@@ -60,6 +68,14 @@ export default class OrderConfigManagerActivityFlowComponent extends Component {
60
68
  */
61
69
  @tracked listeningForClicks = false;
62
70
 
71
+ graphScrollContainer;
72
+ horizontalScrollbar;
73
+ horizontalScrollbarContent;
74
+ isSyncingHorizontalScroll = false;
75
+ onGraphScroll;
76
+ onHorizontalScrollbarScroll;
77
+ onWindowResize;
78
+
63
79
  /**
64
80
  * Initializes the component with the given configuration.
65
81
  * @param {Object} owner - The owner of the component instance.
@@ -85,6 +101,42 @@ export default class OrderConfigManagerActivityFlowComponent extends Component {
85
101
  this.initializeActivityJointModel();
86
102
  this.initializeActivityFlow();
87
103
  this.listenForElementClicks();
104
+ this.updateHorizontalScrollbar();
105
+ }
106
+
107
+ @action setupGraphScrollContainer(element) {
108
+ this.graphScrollContainer = element;
109
+ this.onGraphScroll = () => this.syncHorizontalScrollbarFromGraph();
110
+ this.onWindowResize = () => this.updateHorizontalScrollbar();
111
+
112
+ element.addEventListener('scroll', this.onGraphScroll);
113
+ window.addEventListener('resize', this.onWindowResize);
114
+ this.updateHorizontalScrollbar();
115
+ }
116
+
117
+ @action setupHorizontalScrollbar(element) {
118
+ this.horizontalScrollbar = element;
119
+ this.horizontalScrollbarContent = element.firstElementChild;
120
+ this.onHorizontalScrollbarScroll = () => this.syncGraphFromHorizontalScrollbar();
121
+
122
+ element.addEventListener('scroll', this.onHorizontalScrollbarScroll);
123
+ this.updateHorizontalScrollbar();
124
+ }
125
+
126
+ willDestroy() {
127
+ super.willDestroy(...arguments);
128
+
129
+ if (this.graphScrollContainer && this.onGraphScroll) {
130
+ this.graphScrollContainer.removeEventListener('scroll', this.onGraphScroll);
131
+ }
132
+
133
+ if (this.horizontalScrollbar && this.onHorizontalScrollbarScroll) {
134
+ this.horizontalScrollbar.removeEventListener('scroll', this.onHorizontalScrollbarScroll);
135
+ }
136
+
137
+ if (this.onWindowResize) {
138
+ window.removeEventListener('resize', this.onWindowResize);
139
+ }
88
140
  }
89
141
 
90
142
  /**
@@ -299,14 +351,20 @@ export default class OrderConfigManagerActivityFlowComponent extends Component {
299
351
  * @param {Object} incomingFlow - The incoming serialized activity flow.
300
352
  */
301
353
  deserializeFlow(incomingFlow) {
354
+ const normalizedFlow = normalizeOrderConfigFlow(incomingFlow);
302
355
  const deserializedFlow = {};
303
- const keys = Object.keys(incomingFlow);
356
+ const keys = Object.keys(normalizedFlow);
304
357
  keys.forEach((key) => {
305
- const activity = this.deserializeActivity(incomingFlow[key], incomingFlow);
306
- deserializedFlow[activity.get('code')] = activity;
358
+ const activity = this.deserializeActivity(normalizedFlow[key], normalizedFlow, deserializedFlow);
359
+ if (activity) {
360
+ deserializedFlow[activity.get('code')] = activity;
361
+ }
307
362
  });
308
363
 
309
- this.addDeserializedActivityToGraph(deserializedFlow.created);
364
+ const rootCode = getOrderConfigFlowRootCode(deserializedFlow);
365
+ if (rootCode && deserializedFlow[rootCode]) {
366
+ this.addDeserializedActivityToGraph(deserializedFlow[rootCode]);
367
+ }
310
368
  }
311
369
 
312
370
  /**
@@ -315,8 +373,21 @@ export default class OrderConfigManagerActivityFlowComponent extends Component {
315
373
  * @param {Object} incomingFlow - The entire incoming serialized activity flow for reference.
316
374
  * @returns {Object} Deserialized activity object.
317
375
  */
318
- deserializeActivity(activityObject, incomingFlow) {
319
- const activity = createFlowActivity(activityObject.code, activityObject.status, activityObject.details, activityObject.sequence, activityObject.color, {
376
+ deserializeActivity(activityObject, incomingFlow, deserializedFlow = {}) {
377
+ if (!activityObject || typeof activityObject !== 'object') {
378
+ return;
379
+ }
380
+
381
+ const code = activityObject.code ?? activityObject.key;
382
+ if (!code) {
383
+ return;
384
+ }
385
+
386
+ if (deserializedFlow[code]) {
387
+ return deserializedFlow[code];
388
+ }
389
+
390
+ const activity = createFlowActivity(code, activityObject.status, activityObject.details, activityObject.sequence, activityObject.color, {
320
391
  key: activityObject.key,
321
392
  logic: activityObject.logic ?? [],
322
393
  events: activityObject.events ?? [],
@@ -327,13 +398,14 @@ export default class OrderConfigManagerActivityFlowComponent extends Component {
327
398
  complete: activityObject.complete ?? false,
328
399
  internalId: activityObject.internalId ?? generateUUID(),
329
400
  });
401
+ deserializedFlow[code] = activity;
330
402
  const { activities } = activityObject;
331
403
  if (isArray(activities)) {
332
404
  activity.set(
333
405
  'activities',
334
406
  activities
335
407
  .map((activityKey) => {
336
- return this.deserializeActivity(incomingFlow[activityKey], incomingFlow);
408
+ return this.deserializeActivity(incomingFlow[activityKey], incomingFlow, deserializedFlow);
337
409
  })
338
410
  .filter(Boolean)
339
411
  );
@@ -347,6 +419,10 @@ export default class OrderConfigManagerActivityFlowComponent extends Component {
347
419
  * @param {Object} activity - The activity to add to the graph.
348
420
  */
349
421
  addDeserializedActivityToGraph(activity) {
422
+ if (!activity) {
423
+ return;
424
+ }
425
+
350
426
  const positionals = this.getActivityPositioning(activity);
351
427
  const parentActivity = this.createActivityNode(activity, positionals);
352
428
  const childActivities = activity.get('activities');
@@ -355,6 +431,7 @@ export default class OrderConfigManagerActivityFlowComponent extends Component {
355
431
  this.addActivityNodeTools(parentActivity, positionals);
356
432
  this.addChildActivities(parentActivity, childActivities);
357
433
  this.repositionAllActivities();
434
+ this.resizePaperWidthToFitActivities();
358
435
  }
359
436
 
360
437
  /**
@@ -394,6 +471,79 @@ export default class OrderConfigManagerActivityFlowComponent extends Component {
394
471
  });
395
472
  }
396
473
 
474
+ /**
475
+ * Expands the JointJS paper width so wide activity flows can scroll horizontally.
476
+ *
477
+ * @returns {void}
478
+ */
479
+ resizePaperWidthToFitActivities() {
480
+ if (!this.paper) {
481
+ return;
482
+ }
483
+
484
+ const nodes = Object.values(this.flow)
485
+ .map((activity) => activity.get('node'))
486
+ .filter(Boolean);
487
+
488
+ if (nodes.length === 0) {
489
+ return;
490
+ }
491
+
492
+ const padding = 150;
493
+ const viewportWidth = this.paper.el.parentElement?.clientWidth ?? this.paper.options.width;
494
+ const currentHeight = this.paper.options.height;
495
+ const contentWidth = nodes.reduce((width, node) => {
496
+ const position = node.position();
497
+ const size = node.size();
498
+
499
+ return Math.max(width, position.x + size.width + padding);
500
+ }, 0);
501
+ const paperWidth = Math.max(viewportWidth, contentWidth);
502
+
503
+ this.paper.setDimensions(paperWidth, currentHeight);
504
+ this.paper.el.style.width = contentWidth > viewportWidth ? `${paperWidth}px` : '100%';
505
+ this.paper.el.style.minWidth = '100%';
506
+ next(this, this.updateHorizontalScrollbar);
507
+ }
508
+
509
+ updateHorizontalScrollbar() {
510
+ if (!this.graphScrollContainer || !this.horizontalScrollbar || !this.horizontalScrollbarContent) {
511
+ return;
512
+ }
513
+
514
+ const scrollWidth = this.graphScrollContainer.scrollWidth;
515
+ const clientWidth = this.graphScrollContainer.clientWidth;
516
+ const hasOverflow = scrollWidth > clientWidth + 1;
517
+
518
+ this.hasHorizontalOverflow = hasOverflow;
519
+ this.horizontalScrollbarContent.style.width = hasOverflow ? `${scrollWidth}px` : '100%';
520
+ this.horizontalScrollbar.scrollLeft = this.graphScrollContainer.scrollLeft;
521
+
522
+ const { left, width } = this.graphScrollContainer.getBoundingClientRect();
523
+ this.horizontalScrollbar.style.left = `${left}px`;
524
+ this.horizontalScrollbar.style.width = `${width}px`;
525
+ }
526
+
527
+ syncHorizontalScrollbarFromGraph() {
528
+ if (this.isSyncingHorizontalScroll || !this.horizontalScrollbar || !this.graphScrollContainer) {
529
+ return;
530
+ }
531
+
532
+ this.isSyncingHorizontalScroll = true;
533
+ this.horizontalScrollbar.scrollLeft = this.graphScrollContainer.scrollLeft;
534
+ this.isSyncingHorizontalScroll = false;
535
+ }
536
+
537
+ syncGraphFromHorizontalScrollbar() {
538
+ if (this.isSyncingHorizontalScroll || !this.horizontalScrollbar || !this.graphScrollContainer) {
539
+ return;
540
+ }
541
+
542
+ this.isSyncingHorizontalScroll = true;
543
+ this.graphScrollContainer.scrollLeft = this.horizontalScrollbar.scrollLeft;
544
+ this.isSyncingHorizontalScroll = false;
545
+ }
546
+
397
547
  /**
398
548
  * Creates and adds a link between a parent activity and a child activity on the graph.
399
549
  *
@@ -484,6 +634,7 @@ export default class OrderConfigManagerActivityFlowComponent extends Component {
484
634
  const firstActivity = this.addActivityToGraph(activityObject);
485
635
  lastActivity = firstActivity;
486
636
  });
637
+ this.resizePaperWidthToFitActivities();
487
638
  return;
488
639
  }
489
640
 
@@ -550,6 +701,16 @@ export default class OrderConfigManagerActivityFlowComponent extends Component {
550
701
  return this.flow;
551
702
  }
552
703
 
704
+ /**
705
+ * Coerces activity label fields before passing them to JointJS text helpers.
706
+ *
707
+ * @param {*} value
708
+ * @returns {String}
709
+ */
710
+ activityText(value) {
711
+ return typeof value === 'string' ? value : value == null ? '' : String(value);
712
+ }
713
+
553
714
  /**
554
715
  * Creates a new activity node based on the provided activity and positioning data.
555
716
  * @param {Object} activity - The activity data.
@@ -558,14 +719,17 @@ export default class OrderConfigManagerActivityFlowComponent extends Component {
558
719
  */
559
720
  createActivityNode(activity, positionals = {}) {
560
721
  const { width, height, x, y } = positionals;
561
- const wrappedDetails = joint.util.breakText(activity.get('details'), { width });
722
+ const code = this.activityText(activity.get('code'));
723
+ const status = this.activityText(activity.get('status'));
724
+ const details = this.activityText(activity.get('details'));
725
+ const wrappedDetails = this.activityText(joint.util.breakText(details, { width }));
562
726
  const activityNode = new joint.shapes.fleetbase.Activity({
563
727
  position: { x, y },
564
728
  size: { width, height },
565
729
  attrs: {
566
730
  pill: {
567
731
  ref: 'code',
568
- refWidth: activity.get('code').length * 1.35,
732
+ refWidth: code.length * 1.35,
569
733
  refHeight: 5,
570
734
  refX: -5,
571
735
  refY: -2,
@@ -575,8 +739,8 @@ export default class OrderConfigManagerActivityFlowComponent extends Component {
575
739
  stroke: '#374151',
576
740
  strokeWidth: 1,
577
741
  },
578
- code: { ref: 'pill', text: activity.get('code'), fill: 'white', fontSize: 14, textAnchor: 'left', refX: 15, refY: 20, yAlignment: 'middle' },
579
- status: { text: activity.get('status'), fill: 'white', fontSize: 14, fontWeight: 'bold', refY: 40, refX: 10, textAnchor: 'left' },
742
+ code: { ref: 'pill', text: code, fill: 'white', fontSize: 14, textAnchor: 'left', refX: 15, refY: 20, yAlignment: 'middle' },
743
+ status: { text: status, fill: 'white', fontSize: 14, fontWeight: 'bold', refY: 40, refX: 10, textAnchor: 'left' },
580
744
  details: {
581
745
  text: wrappedDetails,
582
746
  fill: 'white',
@@ -608,7 +772,8 @@ export default class OrderConfigManagerActivityFlowComponent extends Component {
608
772
  const width = 250;
609
773
  const baseHeight = 90;
610
774
  const lineHeight = 10;
611
- const wrappedDetails = joint.util.breakText(activity.get('details'), { width });
775
+ const details = this.activityText(activity.get('details'));
776
+ const wrappedDetails = this.activityText(joint.util.breakText(details, { width }));
612
777
  const numberOfLines = wrappedDetails.split('\n').length;
613
778
  const height = baseHeight + lineHeight * (numberOfLines - 1);
614
779
  let x = 100;
@@ -933,6 +1098,7 @@ export default class OrderConfigManagerActivityFlowComponent extends Component {
933
1098
  target: { id: newActivity.get('id') },
934
1099
  });
935
1100
  link.addTo(this.graph);
1101
+ this.resizePaperWidthToFitActivities();
936
1102
 
937
1103
  return newActivity;
938
1104
  }
@@ -7,6 +7,6 @@ export default class PlaceAddressComponent extends Component {
7
7
  }
8
8
 
9
9
  get addressHtml() {
10
- return placeAddressHtml(this.place);
10
+ return placeAddressHtml(this.place, { showTitle: this.args.showTitle });
11
11
  }
12
12
  }
@@ -9,10 +9,10 @@
9
9
  </div>
10
10
  <div class="order-route-location {{@routeLocationClass}} dark:text-gray-100">
11
11
  <div class="mb-2 flex flex-wrap items-center gap-1.5">
12
- {{#if this.firstStop.place.status_code}}
13
- <Badge @status={{this.firstStop.place.status_code}} />
12
+ {{#if this.firstStop.statusCode}}
13
+ <Badge @status={{this.firstStop.statusCode}} />
14
14
  {{/if}}
15
- {{#if this.firstStop.completed}}
15
+ {{#if this.firstStop.showCompletedFlagBadge}}
16
16
  <Badge @status="success" @hideStatusDot={{true}}>Completed</Badge>
17
17
  {{else if this.firstStop.active}}
18
18
  <Badge @status="indigo" @hideStatusDot={{true}}>Current Stop</Badge>
@@ -20,11 +20,8 @@
20
20
  {{#if this.firstStop.etaSeconds}}
21
21
  <Badge @status="pending" @hideStatusDot={{true}}><span class="font-bold">ETA:</span> {{format-duration this.firstStop.etaSeconds}}</Badge>
22
22
  {{/if}}
23
- {{#if this.firstStop.etaAt}}
24
- <Badge @status="pending" @hideStatusDot={{true}}>{{format-date-fns this.firstStop.etaAt "d MMM HH:mm"}}</Badge>
25
- {{/if}}
26
23
  </div>
27
- <DisplayPlace @place={{this.firstStop.place}} @waypointActions={{@waypointActions}} @hideBadges={{true}} />
24
+ <DisplayPlace @place={{this.firstStop.place}} @waypointActions={{@waypointActions}} @waypointActionContext={{this.firstStop}} @hideBadges={{true}} />
28
25
  </div>
29
26
  </div>
30
27
  {{/if}}
@@ -47,10 +44,10 @@
47
44
  </div>
48
45
  <div class="order-route-location {{@routeLocationClass}} dark:text-gray-100">
49
46
  <div class="mb-2 flex flex-wrap items-center gap-1.5">
50
- {{#if stop.place.status_code}}
51
- <Badge @status={{stop.place.status_code}} />
47
+ {{#if stop.statusCode}}
48
+ <Badge @status={{stop.statusCode}} />
52
49
  {{/if}}
53
- {{#if stop.completed}}
50
+ {{#if stop.showCompletedFlagBadge}}
54
51
  <Badge @status="success" @hideStatusDot={{true}}>Completed</Badge>
55
52
  {{else if stop.active}}
56
53
  <Badge @status="indigo" @hideStatusDot={{true}}>Current Stop</Badge>
@@ -58,11 +55,8 @@
58
55
  {{#if stop.etaSeconds}}
59
56
  <Badge @status="pending" @hideStatusDot={{true}}><span class="font-bold">ETA:</span> {{format-duration stop.etaSeconds}}</Badge>
60
57
  {{/if}}
61
- {{#if stop.etaAt}}
62
- <Badge @status="pending" @hideStatusDot={{true}}>{{format-date-fns stop.etaAt "d MMM HH:mm"}}</Badge>
63
- {{/if}}
64
58
  </div>
65
- <DisplayPlace @place={{stop.place}} @waypointActions={{@waypointActions}} @hideBadges={{true}} />
59
+ <DisplayPlace @place={{stop.place}} @waypointActions={{@waypointActions}} @waypointActionContext={{stop}} @hideBadges={{true}} />
66
60
  </div>
67
61
  </div>
68
62
  {{/each}}
@@ -75,10 +69,10 @@
75
69
  </div>
76
70
  <div class="order-route-location {{@routeLocationClass}} dark:text-gray-100">
77
71
  <div class="mb-2 flex flex-wrap items-center gap-1.5">
78
- {{#if stop.place.status_code}}
79
- <Badge @status={{stop.place.status_code}} />
72
+ {{#if stop.statusCode}}
73
+ <Badge @status={{stop.statusCode}} />
80
74
  {{/if}}
81
- {{#if stop.completed}}
75
+ {{#if stop.showCompletedFlagBadge}}
82
76
  <Badge @status="success" @hideStatusDot={{true}}>Completed</Badge>
83
77
  {{else if stop.active}}
84
78
  <Badge @status="indigo" @hideStatusDot={{true}}>Current Stop</Badge>
@@ -86,11 +80,8 @@
86
80
  {{#if stop.etaSeconds}}
87
81
  <Badge @status="pending" @hideStatusDot={{true}}><span class="font-bold">ETA:</span> {{format-duration stop.etaSeconds}}</Badge>
88
82
  {{/if}}
89
- {{#if stop.etaAt}}
90
- <Badge @status="pending" @hideStatusDot={{true}}>{{format-date-fns stop.etaAt "d MMM HH:mm"}}</Badge>
91
- {{/if}}
92
83
  </div>
93
- <DisplayPlace @place={{stop.place}} @waypointActions={{@waypointActions}} @hideBadges={{true}} />
84
+ <DisplayPlace @place={{stop.place}} @waypointActions={{@waypointActions}} @waypointActionContext={{stop}} @hideBadges={{true}} />
94
85
  </div>
95
86
  </div>
96
87
  {{/each}}
@@ -106,10 +97,10 @@
106
97
  </div>
107
98
  <div class="order-route-location {{@routeLocationClass}} dark:text-gray-100">
108
99
  <div class="mb-2 flex flex-wrap items-center gap-1.5">
109
- {{#if this.lastStop.place.status_code}}
110
- <Badge @status={{this.lastStop.place.status_code}} />
100
+ {{#if this.lastStop.statusCode}}
101
+ <Badge @status={{this.lastStop.statusCode}} />
111
102
  {{/if}}
112
- {{#if this.lastStop.completed}}
103
+ {{#if this.lastStop.showCompletedFlagBadge}}
113
104
  <Badge @status="success" @hideStatusDot={{true}}>Completed</Badge>
114
105
  {{else if this.lastStop.active}}
115
106
  <Badge @status="indigo" @hideStatusDot={{true}}>Current Stop</Badge>
@@ -117,12 +108,9 @@
117
108
  {{#if this.lastStop.etaSeconds}}
118
109
  <Badge @status="pending" @hideStatusDot={{true}}><span class="font-bold">ETA:</span> {{format-duration this.lastStop.etaSeconds}}</Badge>
119
110
  {{/if}}
120
- {{#if this.lastStop.etaAt}}
121
- <Badge @status="pending" @hideStatusDot={{true}}>{{format-date-fns this.lastStop.etaAt "d MMM HH:mm"}}</Badge>
122
- {{/if}}
123
111
  </div>
124
- <DisplayPlace @place={{this.lastStop.place}} @waypointActions={{@waypointActions}} @hideBadges={{true}} />
112
+ <DisplayPlace @place={{this.lastStop.place}} @waypointActions={{@waypointActions}} @waypointActionContext={{this.lastStop}} @hideBadges={{true}} />
125
113
  </div>
126
114
  </div>
127
115
  {{/if}}
128
- </div>
116
+ </div>
@@ -17,20 +17,29 @@ export default class RouteListComponent extends Component {
17
17
  const routePoints = buildRoutePointsFromPayload(this.args.order?.payload);
18
18
 
19
19
  return routePoints
20
- .map((routePoint) => ({
21
- routePoint,
22
- place: routePoint.place,
23
- badgeStyle: this.badgeStyleForStop(routePoint),
24
- trackingStop: this.trackingStopFor(routePoint.place),
25
- routeLeg: this.routeLegFor(routePoint.place),
26
- ...describeRoutePoint(routePoint, this.routeColor),
27
- }))
20
+ .map((routePoint) => {
21
+ const trackingStop = this.trackingStopFor(routePoint.place);
22
+
23
+ return {
24
+ routePoint,
25
+ place: routePoint.place,
26
+ badgeStyle: this.badgeStyleForStop(routePoint),
27
+ trackingStop,
28
+ routeLeg: this.routeLegFor(routePoint.place),
29
+ trackingNumberUuid: routePoint.trackingNumberUuid ?? trackingStop?.tracking_number_uuid,
30
+ ...describeRoutePoint(routePoint, this.routeColor),
31
+ };
32
+ })
28
33
  .map((stop) => ({
29
34
  ...stop,
30
- etaSeconds: stop.routeLeg?.eta_seconds ?? stop.routeLeg?.duration_in_traffic_s ?? stop.routeLeg?.duration_s ?? this.legacyEtaFor(stop.place),
31
- etaAt: stop.routeLeg?.eta_at,
35
+ etaSeconds: this.shouldShowEtaForStop(stop)
36
+ ? (stop.routeLeg?.eta_seconds ?? stop.routeLeg?.duration_in_traffic_s ?? stop.routeLeg?.duration_s ?? this.legacyEtaFor(stop.place))
37
+ : null,
38
+ etaAt: this.shouldShowEtaForStop(stop) ? stop.routeLeg?.eta_at : null,
32
39
  completed: Boolean(stop.trackingStop?.completed),
33
40
  active: this.matchesStop(stop.trackingStop, this.args.order?.tracker_data?.active_stop),
41
+ statusCode: stop.place?.status_code ?? stop.trackingStop?.status,
42
+ showCompletedFlagBadge: this.shouldShowCompletedFlagBadge(stop),
34
43
  }));
35
44
  }
36
45
 
@@ -54,6 +63,10 @@ export default class RouteListComponent extends Component {
54
63
  return this.args.isCollapsible !== false && this.hasExtraStops;
55
64
  }
56
65
 
66
+ get showEta() {
67
+ return this.args.showEta !== false;
68
+ }
69
+
57
70
  badgeStyleForStop(routePoint) {
58
71
  const { markerColor } = describeRoutePoint(routePoint, this.routeColor);
59
72
  const isYellow = markerColor?.toLowerCase?.() === '#facc15' || markerColor?.toLowerCase?.() === '#ca8a04';
@@ -94,6 +107,25 @@ export default class RouteListComponent extends Component {
94
107
  return stop.uuid === activeStop.uuid || stop.public_id === activeStop.public_id;
95
108
  }
96
109
 
110
+ shouldShowCompletedFlagBadge(stop) {
111
+ if (!stop.trackingStop?.completed) {
112
+ return false;
113
+ }
114
+
115
+ const visibleStatus = stop.statusCode ?? stop.place?.status_code ?? stop.trackingStop?.status_code ?? stop.trackingStop?.status ?? stop.place?.status;
116
+ if (typeof visibleStatus !== 'string') {
117
+ return true;
118
+ }
119
+
120
+ return visibleStatus.trim().toLowerCase() !== 'completed';
121
+ }
122
+
123
+ shouldShowEtaForStop(stop) {
124
+ const status = this.args.order?.status;
125
+
126
+ return this.showEta && !['completed', 'canceled'].includes(status) && !stop.trackingStop?.completed;
127
+ }
128
+
97
129
  @action toggleWaypointsCollapse() {
98
130
  this.isWaypointsCollapsed = !this.isWaypointsCollapsed;
99
131
  }
@@ -121,6 +121,47 @@
121
121
  </table>
122
122
  </div>
123
123
  </ContentPanel>
124
+ {{else if @resource.isMultiZoneDistance}}
125
+ <ContentPanel @title="Multi-zone Distance" @open={{true}} @wrapperClass="bordered-top" @panelBodyWrapperClass="p-0i">
126
+ <div class="next-table-wrapper h-auto table-fluid rounded-none overflow-x-auto">
127
+ <table class="table table-fixed w-full">
128
+ <thead>
129
+ <tr>
130
+ <th>Label</th>
131
+ <th>Geography Type</th>
132
+ <th>Geography</th>
133
+ <th>Priority</th>
134
+ <th>Rate</th>
135
+ <th>Unit</th>
136
+ </tr>
137
+ </thead>
138
+ <tbody>
139
+ {{#each @resource.rateFees as |rateFee|}}
140
+ <tr>
141
+ <td>{{n-a rateFee.label}}</td>
142
+ <td>{{smart-humanize rateFee.geography_type}}</td>
143
+ <td>
144
+ {{#if (eq rateFee.geography_type "zone")}}
145
+ {{n-a rateFee.zone.name}}
146
+ {{else if (eq rateFee.geography_type "fallback")}}
147
+ Unmatched route distance
148
+ {{else}}
149
+ {{n-a rateFee.service_area.name}}
150
+ {{/if}}
151
+ </td>
152
+ <td>{{n-a rateFee.priority}}</td>
153
+ <td>{{format-currency (f-to-int rateFee.fee) @resource.currency}}</td>
154
+ <td>{{n-a rateFee.distance_unit}}</td>
155
+ </tr>
156
+ {{else}}
157
+ <tr>
158
+ <td colspan="6" class="text-center text-gray-500 italic">No geographic pricing rules defined</td>
159
+ </tr>
160
+ {{/each}}
161
+ </tbody>
162
+ </table>
163
+ </div>
164
+ </ContentPanel>
124
165
  {{else if @resource.isPerMeter}}
125
166
  <ContentPanel @title={{t "service-rate.fields.per-meter"}} @open={{true}} @wrapperClass="bordered-top">
126
167
  <div class="grid grid-cols-1 lg:grid-cols-2 gap-2">
@@ -158,7 +199,7 @@
158
199
  <div class="field-info-container">
159
200
  <div class="field-name">Algorithm Code</div>
160
201
  <div class="field-value">
161
- <pre class="bg-gray-100 dark:bg-gray-900 font-mono p-3 rounded text-xs overflow-x-auto"><code class="font-mono text-xs">{{n-a @resource.algorithm}}</code></pre>
202
+ <pre class="bg-gray-100 dark:bg-gray-900 font-mono p-3 rounded text-xs overflow-x-auto"><code class="font-mono text-xs whitespace-break-spaces">{{n-a @resource.algorithm}}</code></pre>
162
203
  </div>
163
204
  </div>
164
205
  </ContentPanel>