@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
@@ -10,7 +10,7 @@
10
10
  @disabled={{cannot-write @resource}}
11
11
  />
12
12
 
13
- <InputGroup @name={{t "service-rate.fields.service-order-label"}} @helpText={{t "service-rate.fields.service-order-help-text"}}>
13
+ <InputGroup @name={{t "service-rate.fields.service-order-label"}} @helpText={{t "service-rate.fields.service-order-help-text"}} @required={{true}}>
14
14
  <div class="fleetbase-model-select fleetbase-power-select ember-model-select">
15
15
  <PowerSelect
16
16
  @options={{this.orderConfigActions.allOrderConfigs}}
@@ -209,6 +209,124 @@
209
209
  </div>
210
210
  </div>
211
211
  </ContentPanel>
212
+ {{else if @resource.isMultiZoneDistance}}
213
+ <ContentPanel @title="Multi-zone Distance" @open={{true}} @wrapperClass="bordered-top" @panelBodyWrapperClass="px-0i">
214
+ <div class="px-2 mb-3">
215
+ <InfoBlock
216
+ class="mb-3"
217
+ @text="Price route distance across multiple service areas or zones. Priority controls overlap: if a route segment matches more than one rule, the highest-priority rule is used. Unmatched distance can use a fallback rule."
218
+ @exampleText="Main City: 2.50 per km, Remote Area: 3.75 per km, Fallback: 3.75 per km."
219
+ />
220
+
221
+ <div class="flex flex-row items-center space-x-2">
222
+ <Button @text="Add Rule" @icon="plus" @iconPrefix="fas" @onClick={{@resource.addMultiZoneDistanceRule}} />
223
+ <Button @text="Add Fallback" @icon="route" @iconPrefix="fas" @onClick={{@resource.addMultiZoneDistanceFallbackRule}} />
224
+ </div>
225
+ </div>
226
+
227
+ <div class="overflow-x-auto">
228
+ <div class="next-table-wrapper h-auto table-fluid rounded-none">
229
+ <table class="table table-fixed">
230
+ <thead>
231
+ <tr>
232
+ <th class="w-1/6">Label</th>
233
+ <th class="w-1/6">Geography Type</th>
234
+ <th class="w-1/5">Geography</th>
235
+ <th class="w-1/12">Priority</th>
236
+ <th class="w-1/6">Rate</th>
237
+ <th class="w-1/6">Unit</th>
238
+ <th class="w-12"></th>
239
+ </tr>
240
+ </thead>
241
+ <tbody>
242
+ {{#each @resource.rateFees as |rateFee|}}
243
+ <tr>
244
+ <td>
245
+ <Input @value={{rateFee.label}} placeholder="Rule label" disabled={{cannot-write @resource}} class="form-input w-full" />
246
+ </td>
247
+ <td>
248
+ <div class="fleetbase-model-select fleetbase-power-select ember-model-select">
249
+ <PowerSelect
250
+ @options={{this.geographyTypes}}
251
+ @selected={{find-by "value" rateFee.geography_type this.geographyTypes}}
252
+ @onChange={{fn this.selectRuleGeographyType rateFee}}
253
+ @placeholder="Geography type"
254
+ @triggerClass="form-select form-input"
255
+ @disabled={{cannot-write @resource}}
256
+ as |type|
257
+ >
258
+ {{type.label}}
259
+ </PowerSelect>
260
+ </div>
261
+ </td>
262
+ <td>
263
+ {{#if (eq rateFee.geography_type "zone")}}
264
+ <div class="fleetbase-model-select fleetbase-power-select ember-model-select">
265
+ <PowerSelect
266
+ @options={{this.zones}}
267
+ @selected={{rateFee.zone}}
268
+ @onChange={{fn this.selectRuleZone rateFee}}
269
+ @placeholder="Select zone"
270
+ @triggerClass="form-select form-input"
271
+ @disabled={{cannot-write @resource}}
272
+ as |zone|
273
+ >
274
+ {{zone.name}}
275
+ </PowerSelect>
276
+ </div>
277
+ {{else if (eq rateFee.geography_type "fallback")}}
278
+ <div class="text-sm text-gray-500 dark:text-gray-400">Unmatched route distance</div>
279
+ {{else}}
280
+ <div class="fleetbase-model-select fleetbase-power-select ember-model-select">
281
+ <PowerSelect
282
+ @options={{this.serviceAreas}}
283
+ @selected={{rateFee.service_area}}
284
+ @onChange={{fn this.selectRuleServiceArea rateFee}}
285
+ @placeholder="Select service area"
286
+ @triggerClass="form-select form-input"
287
+ @disabled={{cannot-write @resource}}
288
+ as |serviceArea|
289
+ >
290
+ {{serviceArea.name}}
291
+ </PowerSelect>
292
+ </div>
293
+ {{/if}}
294
+ </td>
295
+ <td>
296
+ <Input @type="number" @value={{rateFee.priority}} placeholder="0" disabled={{cannot-write @resource}} class="form-input w-full" />
297
+ </td>
298
+ <td>
299
+ <MoneyInput class="w-full" @currency={{@resource.currency}} @value={{rateFee.fee}} disabled={{cannot-write @resource}} />
300
+ </td>
301
+ <td>
302
+ <div class="fleetbase-model-select fleetbase-power-select ember-model-select">
303
+ <PowerSelect
304
+ @options={{get-fleet-ops-options "distanceUnits"}}
305
+ @selected={{find-by "value" rateFee.distance_unit (get-fleet-ops-options "distanceUnits")}}
306
+ @onChange={{set-model-attr rateFee "distance_unit"}}
307
+ @placeholder="Distance unit"
308
+ @triggerClass="form-select form-input"
309
+ @disabled={{cannot-write @resource}}
310
+ as |unit|
311
+ >
312
+ {{unit.label}}
313
+ </PowerSelect>
314
+ </div>
315
+ </td>
316
+ <td>
317
+ <Button @type="danger" @icon="trash" @onClick={{fn @resource.removeMultiZoneDistanceRule rateFee}} />
318
+ </td>
319
+ </tr>
320
+ {{else}}
321
+ <tr>
322
+ <td colspan="7" class="text-center text-gray-500 italic">No geographic pricing rules defined</td>
323
+ </tr>
324
+ {{/each}}
325
+ </tbody>
326
+ </table>
327
+ </div>
328
+ </div>
329
+ </ContentPanel>
212
330
  {{else if @resource.isPerMeter}}
213
331
  <ContentPanel @title={{t "service-rate.fields.per-meter"}} @open={{true}} @wrapperClass="bordered-top">
214
332
  <InfoBlock class="mb-5" @text={{t "service-rate.fields.per-meter-text"}} @exampleText={{t "service-rate.fields.per-meter-example-text"}} />
@@ -4,11 +4,29 @@ import { action } from '@ember/object';
4
4
 
5
5
  export default class ServiceRateFormComponent extends Component {
6
6
  @service orderConfigActions;
7
+ @service serviceAreaActions;
7
8
  @service serviceRateActions;
8
9
 
9
10
  constructor() {
10
11
  super(...arguments);
11
12
  this.orderConfigActions.loadAll.perform();
13
+ this.serviceAreaActions.loadAll.perform();
14
+ }
15
+
16
+ get serviceAreas() {
17
+ return this.serviceAreaActions.serviceAreas ?? [];
18
+ }
19
+
20
+ get zones() {
21
+ return Array.from(this.serviceAreas).flatMap((serviceArea) => serviceArea.zones?.toArray?.() ?? serviceArea.zones ?? []);
22
+ }
23
+
24
+ get geographyTypes() {
25
+ return [
26
+ { label: 'Service Area', value: 'service_area' },
27
+ { label: 'Zone', value: 'zone' },
28
+ { label: 'Fallback', value: 'fallback' },
29
+ ];
12
30
  }
13
31
 
14
32
  @action selectOrderConfig(orderConfig) {
@@ -24,10 +42,51 @@ export default class ServiceRateFormComponent extends Component {
24
42
  this.args.resource.resetPerDropFees();
25
43
  } else if (rateCalculationMethod === 'fixed_meter' || rateCalculationMethod === 'fixed_rate') {
26
44
  this.serviceRateActions.generateFixedRateFees(this.args.resource);
45
+ } else if (rateCalculationMethod === 'multi_zone_distance' && !this.args.resource.rateFees.length) {
46
+ this.args.resource.addMultiZoneDistanceRule();
47
+ this.args.resource.addMultiZoneDistanceFallbackRule();
27
48
  }
28
49
  }
29
50
 
30
51
  @action onMaxDistanceChange() {
31
52
  this.serviceRateActions.generateFixedRateFees(this.args.resource);
32
53
  }
54
+
55
+ @action selectRuleGeographyType(rule, { value }) {
56
+ rule.set('selected_geography_type', value);
57
+ rule.set('zone', null);
58
+ rule.set('zone_uuid', null);
59
+ rule.set('service_area', null);
60
+ rule.set('service_area_uuid', null);
61
+ rule.set('is_fallback', value === 'fallback');
62
+ }
63
+
64
+ @action selectRuleServiceArea(rule, serviceArea) {
65
+ rule.set('selected_geography_type', 'service_area');
66
+ rule.set('service_area', serviceArea);
67
+ rule.set('service_area_uuid', serviceArea?.id);
68
+ rule.set('zone', null);
69
+ rule.set('zone_uuid', null);
70
+ rule.set('is_fallback', false);
71
+ }
72
+
73
+ @action selectRuleZone(rule, zone) {
74
+ rule.set('selected_geography_type', 'zone');
75
+ rule.set('zone', zone);
76
+ rule.set('zone_uuid', zone?.id);
77
+ rule.set('service_area', null);
78
+ rule.set('service_area_uuid', null);
79
+ rule.set('is_fallback', false);
80
+ }
81
+
82
+ @action setRuleFallback(rule, isFallback) {
83
+ rule.set('is_fallback', Boolean(isFallback));
84
+ if (isFallback) {
85
+ rule.set('selected_geography_type', 'fallback');
86
+ rule.set('zone', null);
87
+ rule.set('zone_uuid', null);
88
+ rule.set('service_area', null);
89
+ rule.set('service_area_uuid', null);
90
+ }
91
+ }
33
92
  }
@@ -10,7 +10,7 @@ export default class VehicleFormComponent extends Component {
10
10
  @service currentUser;
11
11
  @service notifications;
12
12
  @service modalsManager;
13
- @tracked statusOptions = ['active', 'pending'];
13
+ @tracked statusOptions = ['available', 'pending'];
14
14
 
15
15
  @action updateAvatarUrl(option) {
16
16
  this.args.resource.avatar_url = option.key === 'custom_avatar' ? option.value : [option.value];
@@ -20,7 +20,7 @@
20
20
  <:tooltip>
21
21
  <div>
22
22
  <div class="text-xs font-semibold">{{this.resource.name this.resource.yearMakeModel}}</div>
23
- <div class="text-xs">{{t "this.resource.driver"}}: {{n-a this.resource.driver_name}}</div>
23
+ <div class="text-xs">{{t "resource.driver"}}: {{n-a this.resource.driver_name}}</div>
24
24
  <div class="text-xs">
25
25
  <span>{{t "common.status"}}:</span>
26
26
  <span class="{{if this.resource.online 'text-green-500' 'text-red-400'}}">
@@ -0,0 +1,166 @@
1
+ <div class="details-wrapper" ...attributes>
2
+ <ContentPanel @title="Personnel" @open={{true}} @wrapperClass="bordered-top">
3
+ <div class="space-y-3">
4
+ <div class="flex flex-col space-y-2 lg:flex-row lg:items-center lg:justify-between lg:space-y-0">
5
+ <div>
6
+ <h3 class="text-sm font-semibold text-gray-900 dark:text-gray-100">Vendor personnel</h3>
7
+ <p class="text-xs text-gray-500 dark:text-gray-400">Manage contacts assigned to this vendor account.</p>
8
+ </div>
9
+ <div class="inline-flex overflow-hidden rounded-md border border-gray-200 bg-gray-50 dark:border-gray-700 dark:bg-gray-800">
10
+ <button
11
+ type="button"
12
+ class="inline-flex w-36 items-center justify-center whitespace-nowrap border-r border-gray-200 px-3 py-2 text-xs font-semibold dark:border-gray-700 {{if this.isAddingExistingContact 'bg-blue-600 text-white' 'text-gray-700 dark:text-gray-200'}}"
13
+ {{on "click" (fn this.setMode "existing")}}
14
+ >
15
+ <FaIcon @icon="address-book" class="mr-1.5" />
16
+ Existing contact
17
+ </button>
18
+ <button
19
+ type="button"
20
+ class="inline-flex w-36 items-center justify-center whitespace-nowrap px-3 py-2 text-xs font-semibold {{if this.isCreatingContact 'bg-blue-600 text-white' 'text-gray-700 dark:text-gray-200'}}"
21
+ {{on "click" (fn this.setMode "new")}}
22
+ >
23
+ <FaIcon @icon="user-plus" class="mr-1.5" />
24
+ New contact
25
+ </button>
26
+ </div>
27
+ </div>
28
+
29
+ <form {{on "submit" (perform this.addPersonnel)}} class="rounded-md border border-gray-200 bg-white p-3 shadow-sm dark:border-gray-700 dark:bg-gray-800">
30
+ <div class="space-y-3">
31
+ {{#if this.isAddingExistingContact}}
32
+ <div class="grid grid-cols-1 gap-3 lg:grid-cols-2">
33
+ <InputGroup @name="Contact" @wrapperClass="mb-0i">
34
+ <ModelSelect
35
+ @modelName="contact"
36
+ @selectedModel={{this.selectedContact}}
37
+ @placeholder="Select existing customer contact"
38
+ @query={{hash type="customer"}}
39
+ @triggerClass="form-select form-input"
40
+ @infiniteScroll={{false}}
41
+ @renderInPlace={{true}}
42
+ @onChange={{this.selectContact}}
43
+ as |model|
44
+ >
45
+ <div class="flex flex-row items-center">
46
+ <div class="mr-2 w-6">
47
+ <Image src={{model.photo_url}} @fallbackSrc={{config "defaultValues.contactImage"}} alt={{model.name}} class="h-6 w-6 rounded-md" />
48
+ </div>
49
+ <div class="min-w-0">
50
+ <div class="truncate font-semibold">{{model.name}}</div>
51
+ <div class="truncate text-xs text-gray-500">{{n-a model.email}}</div>
52
+ </div>
53
+ </div>
54
+ </ModelSelect>
55
+ </InputGroup>
56
+
57
+ <InputGroup @name="Role" @wrapperClass="mb-0i">
58
+ <div class="fleetbase-model-select fleetbase-power-select ember-model-select">
59
+ <PowerSelect
60
+ @options={{this.roleOptions}}
61
+ @selected={{this.selectedRole}}
62
+ @onChange={{this.selectRole}}
63
+ @placeholder="Select role"
64
+ @triggerClass="form-select form-input"
65
+ as |role|
66
+ >
67
+ <div class="text-sm">
68
+ <div class="font-semibold normalize-in-trigger">{{role.label}}</div>
69
+ <div class="hide-from-trigger">{{role.description}}</div>
70
+ </div>
71
+ </PowerSelect>
72
+ </div>
73
+ </InputGroup>
74
+ </div>
75
+ {{else}}
76
+ <div class="grid grid-cols-1 gap-3 lg:grid-cols-2">
77
+ <InputGroup @name="Name" @wrapperClass="mb-0i" @value={{this.name}} @placeholder="Personnel name" />
78
+ <InputGroup @name="Email" @wrapperClass="mb-0i" @type="email" @value={{this.email}} @placeholder="email@example.com" />
79
+ </div>
80
+ <div class="grid grid-cols-1 gap-3 lg:grid-cols-2">
81
+ <InputGroup @name="Phone" @wrapperClass="mb-0i">
82
+ <PhoneInput @value={{this.phone}} @onInput={{fn (mut this.phone)}} class="form-input w-full" />
83
+ </InputGroup>
84
+ <InputGroup @name="Role" @wrapperClass="mb-0i">
85
+ <div class="fleetbase-model-select fleetbase-power-select ember-model-select">
86
+ <PowerSelect
87
+ @options={{this.roleOptions}}
88
+ @selected={{this.selectedRole}}
89
+ @onChange={{this.selectRole}}
90
+ @placeholder="Select role"
91
+ @triggerClass="form-select form-input"
92
+ as |role|
93
+ >
94
+ <div class="text-sm">
95
+ <div class="font-semibold normalize-in-trigger">{{role.label}}</div>
96
+ <div class="hide-from-trigger">{{role.description}}</div>
97
+ </div>
98
+ </PowerSelect>
99
+ </div>
100
+ </InputGroup>
101
+ </div>
102
+ {{/if}}
103
+
104
+ <div>
105
+ <Checkbox @value={{this.createLogin}} @onToggle={{this.toggleCreateLogin}}>Create login</Checkbox>
106
+ </div>
107
+
108
+ <div class="flex justify-end">
109
+ <Button
110
+ @buttonType="submit"
111
+ @type="primary"
112
+ @icon="plus"
113
+ @text="Add Personnel"
114
+ @isLoading={{this.addPersonnel.isRunning}}
115
+ @disabled={{not this.canSubmit}}
116
+ />
117
+ </div>
118
+ </div>
119
+ </form>
120
+
121
+ <div class="overflow-hidden rounded-md border border-gray-200 bg-white shadow-sm dark:border-gray-700 dark:bg-gray-800">
122
+ <div class="grid grid-cols-12 border-b border-gray-200 bg-gray-50 px-3 py-2 text-xs font-semibold text-gray-800 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100">
123
+ <div class="col-span-6">Contact</div>
124
+ <div class="col-span-2">Role</div>
125
+ <div class="col-span-2">Status</div>
126
+ <div class="col-span-2 text-right">Actions</div>
127
+ </div>
128
+
129
+ {{#if this.loadPersonnel.isRunning}}
130
+ <div class="flex items-center justify-center p-4 text-sm text-gray-500 dark:text-gray-300">
131
+ <Spinner @iconClass="text-sky-500" />
132
+ <span class="ml-2">Loading personnel...</span>
133
+ </div>
134
+ {{else if this.hasPersonnel}}
135
+ {{#each this.personnels as |personnel|}}
136
+ <div class="grid grid-cols-12 items-center border-b border-gray-100 px-3 py-2 text-xs last:border-b-0 dark:border-gray-700 dark:text-gray-100">
137
+ <div class="col-span-6 flex min-w-0 items-center">
138
+ <Image src={{personnel.photo_url}} @fallbackSrc={{config "defaultValues.contactImage"}} alt={{personnel.name}} class="mr-2 h-7 w-7 rounded-md" />
139
+ <div class="min-w-0">
140
+ <div class="truncate font-semibold">{{or personnel.name personnel.public_id}}</div>
141
+ <div class="truncate text-gray-500 dark:text-gray-400">{{n-a personnel.email}} {{n-a personnel.phone}}</div>
142
+ </div>
143
+ </div>
144
+ <div class="col-span-2 capitalize">{{personnel.role}}</div>
145
+ <div class="col-span-2">
146
+ <Badge @status={{or personnel.status "active"}} />
147
+ </div>
148
+ <div class="col-span-2 flex justify-end">
149
+ <Button
150
+ @type="danger"
151
+ @size="xs"
152
+ @icon="trash"
153
+ @text="Remove"
154
+ @onClick={{perform this.removePersonnel personnel}}
155
+ @isLoading={{this.removePersonnel.isRunning}}
156
+ />
157
+ </div>
158
+ </div>
159
+ {{/each}}
160
+ {{else}}
161
+ <div class="p-4 text-sm text-gray-500 dark:text-gray-400">No personnel added yet.</div>
162
+ {{/if}}
163
+ </div>
164
+ </div>
165
+ </ContentPanel>
166
+ </div>
@@ -0,0 +1,143 @@
1
+ import Component from '@glimmer/component';
2
+ import { inject as service } from '@ember/service';
3
+ import { tracked } from '@glimmer/tracking';
4
+ import { action } from '@ember/object';
5
+ import { task } from 'ember-concurrency';
6
+
7
+ export default class VendorPersonnelManagerComponent extends Component {
8
+ @service fetch;
9
+ @service notifications;
10
+
11
+ @tracked personnels = [];
12
+ @tracked selectedContact;
13
+ @tracked mode = 'existing';
14
+ @tracked name;
15
+ @tracked email;
16
+ @tracked phone;
17
+ @tracked role = 'member';
18
+ @tracked createLogin = true;
19
+
20
+ roleOptions = [
21
+ {
22
+ label: 'Admin',
23
+ value: 'admin',
24
+ description: 'Can manage vendor account settings and personnel.',
25
+ },
26
+ {
27
+ label: 'Member',
28
+ value: 'member',
29
+ description: 'Can access the vendor customer workspace.',
30
+ },
31
+ ];
32
+
33
+ constructor(owner, args) {
34
+ super(owner, args);
35
+ this.loadPersonnel.perform();
36
+ }
37
+
38
+ get selectedRole() {
39
+ return this.roleOptions.find((option) => option.value === this.role);
40
+ }
41
+
42
+ get isAddingExistingContact() {
43
+ return this.mode === 'existing';
44
+ }
45
+
46
+ get isCreatingContact() {
47
+ return this.mode === 'new';
48
+ }
49
+
50
+ get hasPersonnel() {
51
+ return this.personnels.length > 0;
52
+ }
53
+
54
+ get canSubmit() {
55
+ if (this.isAddingExistingContact) {
56
+ return this.selectedContact;
57
+ }
58
+
59
+ return this.name && this.email;
60
+ }
61
+
62
+ @action setMode(mode) {
63
+ this.mode = mode;
64
+ this.clearForm();
65
+ }
66
+
67
+ @action selectContact(contact) {
68
+ this.selectedContact = contact;
69
+ }
70
+
71
+ @action selectRole(role) {
72
+ this.role = role?.value ?? 'member';
73
+ }
74
+
75
+ @action toggleCreateLogin(value) {
76
+ this.createLogin = value;
77
+ }
78
+
79
+ clearForm() {
80
+ this.selectedContact = null;
81
+ this.name = '';
82
+ this.email = '';
83
+ this.phone = '';
84
+ this.role = 'member';
85
+ this.createLogin = true;
86
+ }
87
+
88
+ @task *loadPersonnel() {
89
+ try {
90
+ const response = yield this.fetch.get(`vendors/${this.args.vendor.id}/personnels`, {}, { namespace: 'int/v1' });
91
+ this.personnels = response.personnels ?? [];
92
+ } catch (error) {
93
+ this.notifications.serverError(error);
94
+ }
95
+ }
96
+
97
+ @task *addPersonnel(event) {
98
+ if (event instanceof Event) {
99
+ event.preventDefault();
100
+ }
101
+
102
+ if (!this.canSubmit) {
103
+ this.notifications.warning(this.isAddingExistingContact ? 'Select a contact to add.' : 'Name and email are required.');
104
+ return;
105
+ }
106
+
107
+ try {
108
+ const payload = {
109
+ role: this.role,
110
+ create_login: this.createLogin,
111
+ };
112
+
113
+ if (this.isAddingExistingContact) {
114
+ payload.contact = this.selectedContact?.public_id ?? this.selectedContact?.uuid ?? this.selectedContact?.id;
115
+ } else {
116
+ payload.name = this.name;
117
+ payload.email = this.email;
118
+ payload.phone = this.phone;
119
+ }
120
+
121
+ const response = yield this.fetch.post(`vendors/${this.args.vendor.id}/personnels`, payload, { namespace: 'int/v1' });
122
+ const personnel = response.personnel;
123
+ if (personnel) {
124
+ this.personnels = [...this.personnels.filter((item) => item.contact_uuid !== personnel.contact_uuid), personnel];
125
+ }
126
+
127
+ this.clearForm();
128
+ this.notifications.success('Personnel added.');
129
+ } catch (error) {
130
+ this.notifications.serverError(error);
131
+ }
132
+ }
133
+
134
+ @task *removePersonnel(personnel) {
135
+ try {
136
+ yield this.fetch.delete(`vendors/${this.args.vendor.id}/personnels/${personnel.id}`, {}, { namespace: 'int/v1' });
137
+ this.personnels = this.personnels.filter((item) => item.id !== personnel.id);
138
+ this.notifications.success('Personnel removed.');
139
+ } catch (error) {
140
+ this.notifications.serverError(error);
141
+ }
142
+ }
143
+ }
@@ -20,6 +20,7 @@
20
20
  @zoom={{this.zoom}}
21
21
  @zoomControl={{true}}
22
22
  @onLoad={{this.onMapLoad}}
23
+ @scrollWheelZoom={{false}}
23
24
  {{set-container-dimensions}}
24
25
  as |layers|
25
26
  >
@@ -7,6 +7,7 @@ import { debug } from '@ember/debug';
7
7
  import { task } from 'ember-concurrency';
8
8
  import { renderCompleted, waitForInsertedAndSized } from '@fleetbase/ember-ui/utils/dom';
9
9
  import LeafletTrackingMarkerComponent from '../leaflet-tracking-marker';
10
+ import ensureLeafletDrawEditNamespace from '../../utils/leaflet-draw-namespace-guard';
10
11
 
11
12
  const DEFAULT_CENTER = [1.31, 103.85];
12
13
  const DEFAULT_ZOOM = 11;
@@ -101,13 +102,7 @@ export default class WidgetLiveFleetComponent extends Component {
101
102
  * widget is read-only.
102
103
  */
103
104
  #patchLeafletDrawInitHook() {
104
- const L = typeof window !== 'undefined' ? (window.L ?? window.leaflet) : null;
105
- if (L && !L.Edit) {
106
- L.Edit = {};
107
- }
108
- if (L && !L.Marker) {
109
- L.Marker = {};
110
- }
105
+ ensureLeafletDrawEditNamespace();
111
106
  }
112
107
 
113
108
  /**
@@ -27,7 +27,7 @@
27
27
  {{/if}}
28
28
  </div>
29
29
  <div class="flex-1 min-h-0">
30
- <Chart @type="line" @labels={{this.data.labels}} @datasets={{this.data.datasets}} @options={{this.chartOptions}} />
30
+ <Chart @type="line" @labels={{this.data.labels}} @datasets={{this.chartDatasets}} @options={{this.chartOptions}} />
31
31
  </div>
32
32
  {{/if}}
33
33
  </div>