@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
@@ -3,6 +3,8 @@ import { tracked } from '@glimmer/tracking';
3
3
  import { action } from '@ember/object';
4
4
  import { inject as service } from '@ember/service';
5
5
  import { task } from 'ember-concurrency';
6
+ import getCurrency from '@fleetbase/ember-ui/utils/get-currency';
7
+ import formatMoney from '@fleetbase/ember-accounting/utils/format-money';
6
8
 
7
9
  const PERIODS = ['7d', '30d', '90d'];
8
10
 
@@ -22,17 +24,88 @@ export default class WidgetRevenueTrendComponent extends Component {
22
24
  this.load.perform();
23
25
  }
24
26
 
27
+ get currencyCode() {
28
+ return this.data?.summary?.currency ?? 'USD';
29
+ }
30
+
31
+ get currency() {
32
+ return getCurrency(this.currencyCode) ?? getCurrency('USD');
33
+ }
34
+
35
+ get currencyDivisor() {
36
+ const precision = Number(this.currency?.precision ?? 2);
37
+
38
+ return precision > 0 ? 10 ** precision : 1;
39
+ }
40
+
41
+ get chartDatasets() {
42
+ return (
43
+ this.data?.datasets?.map((dataset) => {
44
+ if (!this.isRevenueDataset(dataset)) {
45
+ return dataset;
46
+ }
47
+
48
+ return {
49
+ ...dataset,
50
+ data: dataset.data?.map((value) => this.normalizeRevenueValue(value)) ?? [],
51
+ };
52
+ }) ?? []
53
+ );
54
+ }
55
+
56
+ isRevenueDataset(dataset) {
57
+ return dataset?.label === 'Revenue' || dataset?.yAxisID === 'y';
58
+ }
59
+
60
+ normalizeRevenueValue(value) {
61
+ const numericValue = Number(value);
62
+
63
+ if (!Number.isFinite(numericValue)) {
64
+ return value;
65
+ }
66
+
67
+ return numericValue / this.currencyDivisor;
68
+ }
69
+
70
+ formatChartCurrency(value) {
71
+ const numericValue = Number(value);
72
+
73
+ if (!Number.isFinite(numericValue)) {
74
+ return value;
75
+ }
76
+
77
+ return formatMoney(numericValue, this.currency.symbol, this.currency.precision, this.currency.thousandSeparator, this.currency.decimalSeparator);
78
+ }
79
+
25
80
  get chartOptions() {
26
81
  return {
27
82
  responsive: true,
28
83
  maintainAspectRatio: false,
29
84
  plugins: {
30
85
  legend: { display: false },
31
- tooltip: { mode: 'index', intersect: false },
86
+ tooltip: {
87
+ mode: 'index',
88
+ intersect: false,
89
+ callbacks: {
90
+ label: (context) => {
91
+ if (this.isRevenueDataset(context.dataset)) {
92
+ return `Revenue: ${this.formatChartCurrency(context.parsed?.y ?? context.raw)}`;
93
+ }
94
+
95
+ return `${context.dataset?.label ?? 'Value'}: ${context.parsed?.y ?? context.raw}`;
96
+ },
97
+ },
98
+ },
32
99
  },
33
100
  scales: {
34
101
  x: { grid: { display: false } },
35
- y: { beginAtZero: true, ticks: { precision: 0 } },
102
+ y: {
103
+ beginAtZero: true,
104
+ ticks: {
105
+ precision: this.currency.precision,
106
+ callback: (value) => this.formatChartCurrency(value),
107
+ },
108
+ },
36
109
  },
37
110
  elements: { point: { radius: 0, hoverRadius: 4 } },
38
111
  };
@@ -1,19 +1,37 @@
1
1
  import Controller from '@ember/controller';
2
- import { tracked } from '@glimmer/tracking';
3
2
  import { inject as service } from '@ember/service';
3
+ import { isArray } from '@ember/array';
4
4
 
5
5
  export default class ManagementContactsCustomersDetailsController extends Controller {
6
+ @service('universe/menu-service') menuService;
7
+ @service customerActions;
6
8
  @service hostRouter;
7
- @tracked tabs = [
8
- {
9
- route: 'management.contacts.customers.details.index',
10
- label: 'Overview',
11
- },
12
- ];
13
- @tracked actionButtons = [
14
- {
15
- icon: 'pencil',
16
- fn: () => this.hostRouter.transitionTo('console.fleet-ops.management.contacts.customers.edit', this.model),
17
- },
18
- ];
9
+ @service intl;
10
+
11
+ get tabs() {
12
+ const registeredTabs = this.menuService.getMenuItems('fleet-ops:component:customer:details');
13
+ return [
14
+ {
15
+ route: 'management.contacts.index.details.index',
16
+ label: 'Overview',
17
+ },
18
+ ...(isArray(registeredTabs) ? registeredTabs : []),
19
+ ];
20
+ }
21
+
22
+ get actionButtons() {
23
+ const buttons = [
24
+ {
25
+ icon: 'pencil',
26
+ fn: () => this.hostRouter.transitionTo('console.fleet-ops.management.contacts.customers.edit', this.model),
27
+ },
28
+ ];
29
+
30
+ const accountActionButton = this.customerActions.accountActionButton(this.model);
31
+ if (accountActionButton) {
32
+ buttons.push(accountActionButton);
33
+ }
34
+
35
+ return buttons;
36
+ }
19
37
  }
@@ -4,6 +4,10 @@ import { inject as service } from '@ember/service';
4
4
  export default class ManagementContactsCustomersController extends ManagementContactsIndexController {
5
5
  @service('customerActions') contactActions;
6
6
 
7
+ get customerActions() {
8
+ return this.contactActions;
9
+ }
10
+
7
11
  /** columns */
8
12
  get columns() {
9
13
  return [
@@ -13,7 +17,7 @@ export default class ManagementContactsCustomersController extends ManagementCon
13
17
  valuePath: 'name',
14
18
 
15
19
  cellComponent: 'table/cell/media-name',
16
- action: this.contactActions.transition.view,
20
+ action: this.customerActions.transition.view,
17
21
  permission: 'fleet-ops view contact',
18
22
  resizable: true,
19
23
  sortable: true,
@@ -75,7 +79,7 @@ export default class ManagementContactsCustomersController extends ManagementCon
75
79
  label: this.intl.t('column.address'),
76
80
  valuePath: 'address',
77
81
  cellComponent: 'table/cell/anchor',
78
- action: this.contactActions.viewPlace,
82
+ action: this.customerActions.viewPlace,
79
83
 
80
84
  resizable: true,
81
85
  sortable: true,
@@ -118,20 +122,27 @@ export default class ManagementContactsCustomersController extends ManagementCon
118
122
  actions: [
119
123
  {
120
124
  label: this.intl.t('common.view-resource', { resource: this.intl.t('resource.customer') }),
121
- fn: this.contactActions.transition.view,
125
+ icon: 'eye',
126
+ fn: this.customerActions.transition.view,
122
127
  permission: 'fleet-ops view contact',
123
128
  },
124
129
  {
125
130
  label: this.intl.t('common.edit-resource', { resource: this.intl.t('resource.customer') }),
126
- fn: this.contactActions.transition.edit,
131
+ icon: 'pencil',
132
+ fn: this.customerActions.transition.edit,
127
133
  permission: 'fleet-ops update contact',
128
134
  },
129
135
  {
130
136
  separator: true,
131
137
  },
138
+ ...this.customerActions.accountRowActionItems(),
139
+ {
140
+ separator: true,
141
+ },
132
142
  {
133
143
  label: this.intl.t('common.delete-resource', { resource: this.intl.t('resource.customer') }),
134
- fn: this.contactActions.delete,
144
+ icon: 'trash',
145
+ fn: this.customerActions.delete,
135
146
  permission: 'fleet-ops delete contact',
136
147
  },
137
148
  ],
@@ -1,19 +1,37 @@
1
1
  import Controller from '@ember/controller';
2
- import { tracked } from '@glimmer/tracking';
3
2
  import { inject as service } from '@ember/service';
3
+ import { isArray } from '@ember/array';
4
4
 
5
5
  export default class ManagementContactsIndexDetailsController extends Controller {
6
+ @service('universe/menu-service') menuService;
7
+ @service contactActions;
6
8
  @service hostRouter;
7
- @tracked tabs = [
8
- {
9
- route: 'management.contacts.index.details.index',
10
- label: 'Overview',
11
- },
12
- ];
13
- @tracked actionButtons = [
14
- {
15
- icon: 'pencil',
16
- fn: () => this.hostRouter.transitionTo('console.fleet-ops.management.contacts.index.edit', this.model),
17
- },
18
- ];
9
+ @service intl;
10
+
11
+ get tabs() {
12
+ const registeredTabs = this.menuService.getMenuItems('fleet-ops:component:contact:details');
13
+ return [
14
+ {
15
+ route: 'management.contacts.index.details.index',
16
+ label: 'Overview',
17
+ },
18
+ ...(isArray(registeredTabs) ? registeredTabs : []),
19
+ ];
20
+ }
21
+
22
+ get actionButtons() {
23
+ const buttons = [
24
+ {
25
+ icon: 'pencil',
26
+ fn: () => this.hostRouter.transitionTo('console.fleet-ops.management.contacts.index.edit', this.model),
27
+ },
28
+ ];
29
+
30
+ const accountActionButton = this.contactActions.accountActionButton(this.model);
31
+ if (accountActionButton) {
32
+ buttons.push(accountActionButton);
33
+ }
34
+
35
+ return buttons;
36
+ }
19
37
  }
@@ -168,19 +168,26 @@ export default class ManagementContactsIndexController extends Controller {
168
168
  actions: [
169
169
  {
170
170
  label: this.intl.t('common.view-resource', { resource: this.intl.t('resource.contact') }),
171
+ icon: 'eye',
171
172
  fn: this.contactActions.transition.view,
172
173
  permission: 'fleet-ops view contact',
173
174
  },
174
175
  {
175
176
  label: this.intl.t('common.edit-resource', { resource: this.intl.t('resource.contact') }),
177
+ icon: 'pencil',
176
178
  fn: this.contactActions.transition.edit,
177
179
  permission: 'fleet-ops update contact',
178
180
  },
179
181
  {
180
182
  separator: true,
181
183
  },
184
+ ...this.contactActions.accountRowActionItems(),
185
+ {
186
+ separator: true,
187
+ },
182
188
  {
183
189
  label: this.intl.t('common.delete-resource', { resource: this.intl.t('resource.contact') }),
190
+ icon: 'trash',
184
191
  fn: this.contactActions.delete,
185
192
  permission: 'fleet-ops delete contact',
186
193
  },
@@ -4,16 +4,62 @@ import { inject as service } from '@ember/service';
4
4
 
5
5
  export default class ManagementIssuesIndexDetailsController extends Controller {
6
6
  @service hostRouter;
7
+ @service issueActions;
8
+
7
9
  @tracked tabs = [
8
10
  {
9
11
  route: 'management.issues.index.details.index',
10
12
  label: 'Overview',
11
13
  },
12
14
  ];
13
- @tracked actionButtons = [
14
- {
15
- icon: 'pencil',
16
- fn: () => this.hostRouter.transitionTo('console.fleet-ops.management.issues.index.edit', this.model),
17
- },
18
- ];
15
+
16
+ get isClosed() {
17
+ return ['closed', 'resolved', 'completed'].includes(this.model?.status);
18
+ }
19
+
20
+ get actionButtons() {
21
+ const workflowItems = [
22
+ {
23
+ label: 'Change Status',
24
+ icon: 'arrows-rotate',
25
+ fn: () => this.issueActions.openStatusModal(this.model, { onSaved: () => this.refreshIssue() }),
26
+ },
27
+ {
28
+ label: 'Assign Issue',
29
+ icon: 'user-check',
30
+ fn: () => this.issueActions.openAssignModal(this.model, { onSaved: () => this.refreshIssue() }),
31
+ },
32
+ ];
33
+
34
+ if (this.isClosed) {
35
+ workflowItems.push({
36
+ label: 'Re-open Issue',
37
+ icon: 'rotate-left',
38
+ fn: () => this.issueActions.confirmReopenIssue(this.model, { onSaved: () => this.refreshIssue() }),
39
+ });
40
+ } else {
41
+ workflowItems.push({
42
+ label: 'Close Issue',
43
+ icon: 'circle-check',
44
+ class: 'text-green-600 dark:text-green-400',
45
+ fn: () => this.issueActions.openCloseIssueModal(this.model, { onSaved: () => this.refreshIssue() }),
46
+ });
47
+ }
48
+
49
+ return [
50
+ {
51
+ icon: 'pencil',
52
+ fn: () => this.hostRouter.transitionTo('console.fleet-ops.management.issues.index.edit', this.model),
53
+ },
54
+ {
55
+ icon: 'ellipsis',
56
+ type: 'default',
57
+ items: workflowItems,
58
+ },
59
+ ];
60
+ }
61
+
62
+ refreshIssue() {
63
+ return this.model?.reload?.();
64
+ }
19
65
  }
@@ -4,7 +4,7 @@ import { inject as service } from '@ember/service';
4
4
  import { action } from '@ember/object';
5
5
  import { task } from 'ember-concurrency';
6
6
 
7
- const DEFAULT_PROPERTIES = { status: 'active' };
7
+ const DEFAULT_PROPERTIES = { status: 'available' };
8
8
 
9
9
  export default class ManagementVehiclesIndexNewController extends Controller {
10
10
  @service store;
@@ -9,6 +9,10 @@ export default class ManagementVendorsIndexDetailsController extends Controller
9
9
  route: 'management.vendors.index.details.index',
10
10
  label: 'Overview',
11
11
  },
12
+ {
13
+ route: 'management.vendors.index.details.personnel',
14
+ label: 'Personnel',
15
+ },
12
16
  ];
13
17
  @tracked actionButtons = [
14
18
  {
@@ -1,5 +1,6 @@
1
1
  import Controller, { inject as controller } from '@ember/controller';
2
2
  import { tracked } from '@glimmer/tracking';
3
+ import { action } from '@ember/object';
3
4
  import { inject as service } from '@ember/service';
4
5
  import { isArray } from '@ember/array';
5
6
  import { task } from 'ember-concurrency';
@@ -19,6 +20,7 @@ export default class OperationsOrdersIndexDetailsController extends Controller {
19
20
  @tracked routingControl;
20
21
  @tracked routingCompleted = false;
21
22
  @tracked realtimeOrderPublicId = null;
23
+ @tracked proofReloadToken = 0;
22
24
 
23
25
  get routePoints() {
24
26
  return buildRoutePointsFromPayload(this.model?.payload);
@@ -97,16 +99,14 @@ export default class OperationsOrdersIndexDetailsController extends Controller {
97
99
  disabled: this.model.status === 'canceled',
98
100
  fn: () =>
99
101
  this.orderActions.updateActivity(this.model, {
100
- onFinish: () => {
101
- this.refresh.perform();
102
- },
102
+ onFinish: this.handleActivityModalFinish,
103
103
  }),
104
104
  },
105
105
  {
106
- text: 'Unassign driver',
107
- icon: 'user-xmark',
108
- disabled: this.model.status === 'canceled' || !this.model.driver_assigned,
109
- fn: () => this.orderActions.unassignDriver(this.model),
106
+ text: this.model.has_driver_assigned ? 'Unassign driver' : 'Assign driver',
107
+ icon: this.model.has_driver_assigned ? 'user-xmark' : 'edit',
108
+ disabled: this.model.has_driver_assigned ? !this.model.hasActiveStatus || !this.model.driver_assigned : !this.model.hasActiveStatus,
109
+ fn: () => (this.model.has_driver_assigned ? this.orderActions.unassignDriver(this.model) : this.orderActions.assignDriver(this.model)),
110
110
  },
111
111
  {
112
112
  text: 'View order label',
@@ -154,6 +154,14 @@ export default class OperationsOrdersIndexDetailsController extends Controller {
154
154
  ];
155
155
  }
156
156
 
157
+ @action handleActivityModalFinish(options) {
158
+ this.refresh.perform();
159
+
160
+ if (options?.proofCreated) {
161
+ this.proofReloadToken++;
162
+ }
163
+ }
164
+
157
165
  @task *refresh() {
158
166
  try {
159
167
  yield this.hostRouter.refresh();
@@ -271,7 +271,7 @@ export default class OperationsOrdersIndexController extends Controller {
271
271
  },
272
272
  {
273
273
  label: this.intl.t('column.transaction'),
274
- cellComponent: 'table/cell/base',
274
+ cellComponent: 'table/cell/currency',
275
275
  valuePath: 'transaction_amount',
276
276
  resizable: true,
277
277
  hidden: true,
@@ -385,6 +385,20 @@ export default class OperationsOrdersIndexController extends Controller {
385
385
  permission: 'fleet-ops dispatch order',
386
386
  isVisible: (order) => order.canBeDispatched,
387
387
  },
388
+ {
389
+ label: this.intl.t('common.assign-driver'),
390
+ icon: 'edit',
391
+ fn: this.orderActions.assignDriver,
392
+ permission: 'fleet-ops update order',
393
+ isVisible: (order) => !order.has_driver_assigned,
394
+ },
395
+ {
396
+ label: this.intl.t('common.unassign-driver'),
397
+ icon: 'edit',
398
+ fn: this.orderActions.unassignDriver,
399
+ permission: 'fleet-ops update order',
400
+ isVisible: (order) => order.has_driver_assigned,
401
+ },
388
402
  {
389
403
  label: this.intl.t('common.cancel-resource', { resource: this.intl.t('resource.order') }),
390
404
  icon: 'ban',
@@ -117,6 +117,9 @@ export default {
117
117
  // Create registries
118
118
  this.createRegistries(registryService);
119
119
 
120
+ // // Register console home guidance
121
+ // this.registerHomeComponents(registryService);
122
+
120
123
  // Setup customer portal
121
124
  const isCustomerPortalInstalled = universe.extensionManager.isInstalled('@fleetbase/customer-portal-engine');
122
125
  if (isCustomerPortalInstalled) {
@@ -216,13 +219,23 @@ export default {
216
219
  category: 'Analytics',
217
220
  default: false,
218
221
  }),
222
+ new Widget({
223
+ id: 'fleet-ops-live-fleet-widget',
224
+ name: 'Live Fleet Map',
225
+ description: 'Real-time driver positions and active routes.',
226
+ icon: 'map-location-dot',
227
+ component: new ExtensionComponent('@fleetbase/fleetops-engine', 'widget/live-fleet'),
228
+ grid_options: { w: 8, h: 11, minW: 8, minH: 8 },
229
+ category: 'Maps',
230
+ default: true,
231
+ }),
219
232
  new Widget({
220
233
  id: 'fleet-ops-revenue-trend-widget',
221
234
  name: 'Revenue Trend',
222
235
  description: 'Revenue over time with period comparison.',
223
236
  icon: 'chart-line',
224
237
  component: new ExtensionComponent('@fleetbase/fleetops-engine', 'widget/revenue-trend'),
225
- grid_options: { w: 6, h: 6, minW: 5, minH: 5 },
238
+ grid_options: { w: 4, h: 11, minW: 4, minH: 8 },
226
239
  category: 'Analytics',
227
240
  default: true,
228
241
  }),
@@ -254,7 +267,7 @@ export default {
254
267
  component: new ExtensionComponent('@fleetbase/fleetops-engine', 'widget/top-drivers'),
255
268
  grid_options: { w: 6, h: 6, minW: 5, minH: 5 },
256
269
  category: 'Analytics',
257
- default: false,
270
+ default: true,
258
271
  }),
259
272
  new Widget({
260
273
  id: 'fleet-ops-fuel-efficiency-widget',
@@ -296,21 +309,15 @@ export default {
296
309
  category: 'Analytics',
297
310
  default: false,
298
311
  }),
299
- new Widget({
300
- id: 'fleet-ops-live-fleet-widget',
301
- name: 'Live Fleet Map',
302
- description: 'Real-time driver positions and active routes.',
303
- icon: 'map-location-dot',
304
- component: new ExtensionComponent('@fleetbase/fleetops-engine', 'widget/live-fleet'),
305
- grid_options: { w: 12, h: 10, minW: 8, minH: 8 },
306
- category: 'Maps',
307
- default: true,
308
- }),
309
312
  ];
310
313
 
311
314
  widgetService.registerWidgets('dashboard', widgets);
312
315
  },
313
316
 
317
+ registerHomeComponents(registryService) {
318
+ registryService.registerRenderableComponent('console:home:before-dashboard', new ExtensionComponent('@fleetbase/fleetops-engine', 'home/getting-started-guidance'));
319
+ },
320
+
314
321
  createRegistries(registryService) {
315
322
  registryService.createRegistries([
316
323
  'engine:fleet-ops',
@@ -1,38 +1 @@
1
- import { helper } from '@ember/component/helper';
2
-
3
- export function formatDurationValue(seconds) {
4
- const value = Number(seconds);
5
-
6
- if (!Number.isFinite(value)) {
7
- return '0s';
8
- }
9
-
10
- const totalSeconds = Math.max(0, Math.ceil(value));
11
- const days = Math.floor(totalSeconds / 86400);
12
- const hours = Math.floor((totalSeconds % 86400) / 3600);
13
- const minutes = Math.floor((totalSeconds % 3600) / 60);
14
- const remainingSeconds = totalSeconds % 60;
15
- const parts = [];
16
-
17
- if (days) {
18
- parts.push(`${days}d`);
19
- }
20
-
21
- if (hours) {
22
- parts.push(`${hours}h`);
23
- }
24
-
25
- if (!days && minutes) {
26
- parts.push(`${minutes}m`);
27
- }
28
-
29
- if (!days && !hours && parts.length < 2 && remainingSeconds) {
30
- parts.push(`${remainingSeconds}s`);
31
- }
32
-
33
- return parts.length ? parts.join(' ') : '0s';
34
- }
35
-
36
- export default helper(function formatDuration([seconds]) {
37
- return formatDurationValue(seconds);
38
- });
1
+ export { default, formatDurationValue } from '@fleetbase/ember-ui/helpers/format-duration';
@@ -1,6 +1,6 @@
1
1
  import { helper } from '@ember/component/helper';
2
2
  import placeAddressHtml from '../utils/place-address-html';
3
3
 
4
- export default helper(function placeAddressHtmlHelper([place]) {
5
- return placeAddressHtml(place);
4
+ export default helper(function placeAddressHtmlHelper([place], hash = {}) {
5
+ return placeAddressHtml(place, hash);
6
6
  });
@@ -1,15 +1,14 @@
1
- import loadLeafletPlugins from '@fleetbase/ember-ui/utils/load-leaflet-plugins';
1
+ import { debug } from '@ember/debug';
2
+ import ensureLeafletPluginsReady from '../utils/leaflet-plugin-loader';
2
3
 
3
4
  export function initialize() {
4
5
  let waitForLeaflet = setInterval(() => {
5
6
  let leafletLoaded = window.L !== undefined;
6
7
  if (leafletLoaded) {
7
- loadLeafletPlugins({
8
- scripts: ['leaflet.contextmenu.js', 'leaflet.draw-src.js'],
9
- stylesheets: ['leaflet.contextmenu.css', 'leaflet.draw.css'],
10
- globalIndicatorKey: 'fleetopsLeafletPluginsLoaded',
11
- });
12
8
  clearInterval(waitForLeaflet);
9
+ ensureLeafletPluginsReady().catch((error) => {
10
+ debug(error.message);
11
+ });
13
12
  }
14
13
  }, 100);
15
14
  }
@@ -0,0 +1,51 @@
1
+ import TooltipLayer from 'ember-leaflet/components/tooltip-layer';
2
+
3
+ function hasMethod(object, methodName) {
4
+ return typeof object?.[methodName] === 'function';
5
+ }
6
+
7
+ export function initialize() {
8
+ const prototype = TooltipLayer?.prototype;
9
+ if (!prototype || prototype.__fleetopsTooltipLayerPatched) {
10
+ return;
11
+ }
12
+
13
+ const addToContainer = prototype.addToContainer;
14
+ const removeFromContainer = prototype.removeFromContainer;
15
+ const removePopupListeners = prototype._removePopupListeners;
16
+
17
+ prototype.addToContainer = function () {
18
+ const parentLayer = this.args?.parent?._layer;
19
+ if (!this._layer || !hasMethod(parentLayer, 'bindTooltip')) {
20
+ return;
21
+ }
22
+
23
+ return addToContainer.call(this);
24
+ };
25
+
26
+ prototype.removeFromContainer = function () {
27
+ const parentLayer = this.args?.parent?._layer;
28
+ if (!hasMethod(parentLayer, 'unbindTooltip')) {
29
+ return;
30
+ }
31
+
32
+ return removeFromContainer.call(this);
33
+ };
34
+
35
+ prototype._removePopupListeners = function () {
36
+ const parentMap = this.args?.parent?._layer?._map;
37
+ if (!hasMethod(parentMap, 'removeEventListener')) {
38
+ return;
39
+ }
40
+
41
+ return removePopupListeners.call(this);
42
+ };
43
+
44
+ Object.defineProperty(prototype, '__fleetopsTooltipLayerPatched', {
45
+ value: true,
46
+ });
47
+ }
48
+
49
+ export default {
50
+ initialize,
51
+ };
@@ -28,6 +28,6 @@ export default class ManagementIssuesIndexDetailsRoute extends Route {
28
28
  };
29
29
 
30
30
  model({ public_id }) {
31
- return this.store.queryRecord('issue', { public_id, single: true, with: ['driver', 'vehicle', 'assignee', 'reporter'] });
31
+ return this.store.queryRecord('issue', { public_id, single: true, with: ['driver', 'vehicle', 'assignee', 'reporter', 'order', 'files'] });
32
32
  }
33
33
  }