@fleetbase/fleetops-engine 0.6.53 → 0.6.55

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 (212) hide show
  1. package/addon/components/admin/routing-settings.hbs +2 -1
  2. package/addon/components/cell/attached-vehicle.hbs +10 -0
  3. package/addon/components/cell/attached-vehicle.js +58 -0
  4. package/addon/components/cell/device-identity.hbs +24 -0
  5. package/addon/components/cell/device-identity.js +129 -0
  6. package/addon/components/cell/driver-identity.hbs +33 -0
  7. package/addon/components/cell/driver-identity.js +143 -0
  8. package/addon/components/cell/equipment-identity.hbs +5 -0
  9. package/addon/components/cell/equipment-identity.js +48 -0
  10. package/addon/components/cell/part-identity.hbs +5 -0
  11. package/addon/components/cell/part-identity.js +63 -0
  12. package/addon/components/cell/telematic-device.hbs +38 -0
  13. package/addon/components/cell/telematic-device.js +44 -0
  14. package/addon/components/cell/telematic-provider.hbs +33 -16
  15. package/addon/components/cell/telematic-provider.js +50 -8
  16. package/addon/components/cell/vehicle-identity.hbs +33 -0
  17. package/addon/components/cell/vehicle-identity.js +138 -0
  18. package/addon/components/device/details.hbs +106 -135
  19. package/addon/components/device/details.js +174 -1
  20. package/addon/components/device/form.hbs +2 -2
  21. package/addon/components/device/form.js +7 -0
  22. package/addon/components/device/panel-header.hbs +46 -21
  23. package/addon/components/device/panel-header.js +49 -1
  24. package/addon/components/device/panel-tabs/events.hbs +30 -0
  25. package/addon/components/device/panel-tabs/events.js +128 -0
  26. package/addon/components/device/panel-tabs/sensors.hbs +30 -0
  27. package/addon/components/device/panel-tabs/sensors.js +93 -0
  28. package/addon/components/device/panel-tabs/vehicle.hbs +68 -0
  29. package/addon/components/device/panel-tabs/vehicle.js +90 -0
  30. package/addon/components/equipment/form.js +8 -1
  31. package/addon/components/layout/fleet-ops-sidebar/operations-monitor.js +76 -11
  32. package/addon/components/layout/fleet-ops-sidebar.hbs +1 -1
  33. package/addon/components/layout/fleet-ops-sidebar.js +101 -17
  34. package/addon/components/map/drawer/device-event-listing.js +2 -0
  35. package/addon/components/map/drawer/driver-listing.js +2 -0
  36. package/addon/components/map/drawer/place-listing.js +2 -0
  37. package/addon/components/map/drawer/position-listing.hbs +11 -6
  38. package/addon/components/map/drawer/position-listing.js +17 -4
  39. package/addon/components/map/drawer/vehicle-listing.js +2 -0
  40. package/addon/components/order/form/details.hbs +1 -43
  41. package/addon/components/order/form/details.js +18 -53
  42. package/addon/components/order/form/orchestrator-constraints.hbs +40 -0
  43. package/addon/components/order/form/orchestrator-constraints.js +61 -0
  44. package/addon/components/order/form/payload.js +48 -2
  45. package/addon/components/order/form/route.js +15 -0
  46. package/addon/components/order/form/service-rate.hbs +62 -11
  47. package/addon/components/order/form/service-rate.js +124 -2
  48. package/addon/components/order/form.hbs +2 -0
  49. package/addon/components/sensor/panel-header.hbs +47 -15
  50. package/addon/components/sensor/panel-header.js +59 -1
  51. package/addon/components/table/cell/dropdown/action-item.hbs +31 -0
  52. package/addon/components/table/cell/dropdown/action-item.js +62 -0
  53. package/addon/components/table/cell/dropdown.hbs +28 -0
  54. package/addon/components/table/cell/dropdown.js +91 -0
  55. package/addon/components/telematic/details.hbs +7 -1
  56. package/addon/components/telematic/form.js +16 -8
  57. package/addon/components/vendor/panel-header.hbs +82 -0
  58. package/addon/components/vendor/panel-header.js +48 -0
  59. package/addon/components/work-order/details.hbs +5 -1
  60. package/addon/components/work-order/form.hbs +26 -4
  61. package/addon/components/work-order/form.js +5 -1
  62. package/addon/controllers/connectivity/devices/index/details/events.js +244 -1
  63. package/addon/controllers/connectivity/devices/index/details/sensors.js +199 -0
  64. package/addon/controllers/connectivity/devices/index/details/vehicle.js +122 -0
  65. package/addon/controllers/connectivity/devices/index/details.js +12 -0
  66. package/addon/controllers/connectivity/devices/index.js +144 -129
  67. package/addon/controllers/connectivity/events/details.js +3 -0
  68. package/addon/controllers/connectivity/events/index.js +187 -11
  69. package/addon/controllers/connectivity/sensors/index.js +156 -14
  70. package/addon/controllers/connectivity/telematics/details/attachments.js +220 -54
  71. package/addon/controllers/connectivity/telematics/details/devices.js +170 -129
  72. package/addon/controllers/connectivity/telematics/details/events.js +39 -11
  73. package/addon/controllers/connectivity/telematics/details/sensors.js +39 -19
  74. package/addon/controllers/maintenance/equipment/index.js +1 -2
  75. package/addon/controllers/maintenance/parts/index.js +1 -2
  76. package/addon/controllers/maintenance/work-orders/index.js +18 -1
  77. package/addon/controllers/management/drivers/index.js +82 -42
  78. package/addon/controllers/management/fuel-reports/index.js +63 -4
  79. package/addon/controllers/management/issues/index.js +63 -4
  80. package/addon/controllers/management/vehicles/index.js +33 -6
  81. package/addon/routes/connectivity/devices/index/details/events.js +37 -1
  82. package/addon/routes/connectivity/devices/index/details/sensors.js +35 -0
  83. package/addon/routes/connectivity/devices/index/details/vehicle.js +29 -0
  84. package/addon/routes/connectivity/devices/index.js +7 -0
  85. package/addon/routes/connectivity/events/details.js +3 -0
  86. package/addon/routes/connectivity/events/index.js +6 -2
  87. package/addon/routes/connectivity/sensors/index.js +7 -2
  88. package/addon/routes/connectivity/telematics/details/attachments.js +72 -9
  89. package/addon/routes/connectivity/telematics/details/devices.js +6 -0
  90. package/addon/routes/connectivity/telematics/details/sensors.js +1 -0
  91. package/addon/routes/connectivity/telematics/details.js +1 -1
  92. package/addon/routes/connectivity/telematics/edit.js +1 -1
  93. package/addon/routes/maintenance/work-orders/index.js +1 -0
  94. package/addon/routes.js +2 -0
  95. package/addon/services/device-actions.js +30 -0
  96. package/addon/services/device-event-actions.js +1 -1
  97. package/addon/services/equipment-actions.js +1 -0
  98. package/addon/services/order-creation.js +54 -1
  99. package/addon/services/order-validation.js +3 -2
  100. package/addon/services/sensor-actions.js +3 -0
  101. package/addon/services/service-rate-actions.js +40 -1
  102. package/addon/services/vehicle-actions.js +24 -12
  103. package/addon/services/vendor-actions.js +3 -0
  104. package/addon/styles/fleetops-engine.css +28 -4
  105. package/addon/templates/connectivity/devices/index/details/events.hbs +85 -1
  106. package/addon/templates/connectivity/devices/index/details/sensors.hbs +73 -0
  107. package/addon/templates/connectivity/devices/index/details/vehicle.hbs +147 -0
  108. package/addon/templates/connectivity/telematics/details/attachments.hbs +198 -121
  109. package/addon/templates/connectivity/telematics/details/devices.hbs +4 -1
  110. package/addon/templates/connectivity/telematics/details/events.hbs +4 -1
  111. package/addon/templates/connectivity/telematics/details/sensors.hbs +3 -0
  112. package/addon/templates/management/vehicles/index/edit.hbs +1 -1
  113. package/addon/templates/management/vendors/index/details.hbs +2 -2
  114. package/addon/templates/settings/notifications.hbs +2 -1
  115. package/addon/templates/settings/routing.hbs +1 -0
  116. package/addon/utils/device-table-columns.js +288 -0
  117. package/addon/utils/fleet-ops-options.js +38 -0
  118. package/addon/utils/identity-cell-resource.js +20 -0
  119. package/addon/utils/map-drawer-dropdown-position.js +47 -0
  120. package/addon/utils/trackable-option.js +93 -0
  121. package/app/components/cell/attached-vehicle.js +1 -0
  122. package/app/components/cell/device-identity.js +1 -0
  123. package/app/components/cell/driver-identity.js +1 -0
  124. package/app/components/cell/equipment-identity.js +1 -0
  125. package/app/components/cell/part-identity.js +1 -0
  126. package/app/components/cell/telematic-device.js +1 -0
  127. package/app/components/cell/vehicle-identity.js +1 -0
  128. package/app/components/device/panel-tabs/events.hbs +30 -0
  129. package/app/components/device/panel-tabs/events.js +128 -0
  130. package/app/components/device/panel-tabs/sensors.hbs +30 -0
  131. package/app/components/device/panel-tabs/sensors.js +93 -0
  132. package/app/components/device/panel-tabs/vehicle.hbs +68 -0
  133. package/app/components/device/panel-tabs/vehicle.js +90 -0
  134. package/app/components/order/form/orchestrator-constraints.js +1 -0
  135. package/app/components/table/cell/dropdown/action-item.js +1 -0
  136. package/app/components/table/cell/dropdown.js +1 -0
  137. package/app/components/vendor/panel-header.js +1 -0
  138. package/app/controllers/connectivity/devices/index/details/sensors.js +1 -0
  139. package/app/controllers/connectivity/devices/index/details/vehicle.js +1 -0
  140. package/app/controllers/connectivity/events/{index/details.js → details.js} +1 -1
  141. package/app/routes/connectivity/devices/index/details/sensors.js +1 -0
  142. package/app/routes/connectivity/devices/index/details/vehicle.js +1 -0
  143. package/app/routes/connectivity/events/{index/details.js → details.js} +1 -1
  144. package/app/templates/connectivity/devices/index/details/sensors.js +1 -0
  145. package/app/templates/connectivity/devices/index/details/vehicle.js +1 -0
  146. package/app/templates/connectivity/events/{index/details.js → details.js} +1 -1
  147. package/app/utils/map-drawer-dropdown-position.js +1 -0
  148. package/app/utils/trackable-option.js +1 -0
  149. package/composer.json +1 -1
  150. package/extension.json +1 -1
  151. package/package.json +4 -4
  152. package/server/migrations/2025_08_28_054930_create_work_orders_table.php +1 -0
  153. package/server/migrations/2026_06_18_000001_add_category_to_work_orders_table.php +32 -0
  154. package/server/seeders/Testing/MaintenanceSeeder.php +5 -4
  155. package/server/src/Console/Commands/ProcessMaintenanceTriggers.php +1 -0
  156. package/server/src/Console/Commands/SyncTelematics.php +89 -0
  157. package/server/src/Exceptions/TelematicProviderException.php +13 -2
  158. package/server/src/Http/Controllers/Api/v1/OrderController.php +6 -21
  159. package/server/src/Http/Controllers/Internal/v1/DeviceController.php +55 -1
  160. package/server/src/Http/Controllers/Internal/v1/DeviceEventController.php +17 -6
  161. package/server/src/Http/Controllers/Internal/v1/FleetController.php +9 -0
  162. package/server/src/Http/Controllers/Internal/v1/LiveController.php +178 -1
  163. package/server/src/Http/Controllers/Internal/v1/MaintenanceScheduleController.php +1 -0
  164. package/server/src/Http/Controllers/Internal/v1/OrderController.php +4 -32
  165. package/server/src/Http/Controllers/Internal/v1/SearchController.php +2 -2
  166. package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +4 -4
  167. package/server/src/Http/Controllers/Internal/v1/ServiceRateController.php +82 -13
  168. package/server/src/Http/Filter/DeviceEventFilter.php +65 -0
  169. package/server/src/Http/Filter/DeviceFilter.php +80 -0
  170. package/server/src/Http/Filter/SensorFilter.php +125 -0
  171. package/server/src/Http/Filter/VehicleFilter.php +13 -0
  172. package/server/src/Http/Requests/CreateDriverRequest.php +14 -13
  173. package/server/src/Http/Requests/Internal/CreateDriverRequest.php +1 -0
  174. package/server/src/Http/Resources/v1/Driver.php +11 -2
  175. package/server/src/Http/Resources/v1/Index/Vehicle.php +19 -0
  176. package/server/src/Http/Resources/v1/WorkOrder.php +1 -0
  177. package/server/src/Jobs/FinalizeApiOrderCreation.php +47 -0
  178. package/server/src/Jobs/FinalizeInternalOrderCreation.php +35 -0
  179. package/server/src/Jobs/NotifyBulkAssignedDriver.php +47 -0
  180. package/server/src/Jobs/SyncTelematicDevicesJob.php +180 -123
  181. package/server/src/Models/Device.php +17 -1
  182. package/server/src/Models/DeviceEvent.php +82 -1
  183. package/server/src/Models/Driver.php +16 -0
  184. package/server/src/Models/Equipment.php +55 -0
  185. package/server/src/Models/Sensor.php +15 -2
  186. package/server/src/Models/Telematic.php +1 -1
  187. package/server/src/Models/Vehicle.php +5 -1
  188. package/server/src/Models/WorkOrder.php +5 -2
  189. package/server/src/Observers/DriverObserver.php +3 -3
  190. package/server/src/Observers/FleetObserver.php +23 -0
  191. package/server/src/Observers/VehicleObserver.php +3 -3
  192. package/server/src/Providers/FleetOpsServiceProvider.php +2 -0
  193. package/server/src/Support/LiveCacheService.php +1 -1
  194. package/server/src/Support/Telematics/Providers/AfaqyProvider.php +143 -20
  195. package/server/src/Support/Telematics/Providers/FlespiProvider.php +89 -15
  196. package/server/src/Support/Telematics/Providers/GeotabProvider.php +123 -35
  197. package/server/src/Support/Telematics/Providers/SafeeProvider.php +70 -5
  198. package/server/src/Support/Telematics/Providers/SamsaraProvider.php +122 -14
  199. package/server/src/Support/Telematics/TelematicService.php +330 -30
  200. package/server/src/routes.php +1 -1
  201. package/server/tests/DeviceFilterTest.php +45 -0
  202. package/server/tests/DriverVehicleStatusDefaultTest.php +19 -2
  203. package/server/tests/EquipmentAssignmentMorphTypeTest.php +35 -0
  204. package/server/tests/OperationsMonitorEndpointTest.php +38 -0
  205. package/server/tests/OrderQueuedClosureRegressionTest.php +36 -0
  206. package/server/tests/ServiceRateRouteValidationTest.php +101 -0
  207. package/server/tests/TelematicsHardeningTest.php +598 -6
  208. package/server/tests/VehicleDriverAssignmentTest.php +27 -0
  209. package/translations/en-us.yaml +1 -0
  210. package/addon/controllers/connectivity/events/index/details.js +0 -3
  211. package/addon/routes/connectivity/events/index/details.js +0 -3
  212. /package/addon/templates/connectivity/events/{index/details.hbs → details.hbs} +0 -0
@@ -1,16 +1,33 @@
1
- <div ...attributes>
2
- <button
3
- type="button"
4
- disabled={{and @column.permission (cannot @column.permission)}}
5
- {{on "click" this.onClick}}
6
- class="flex min-w-0 max-w-md items-start gap-2 py-1 text-left"
7
- >
8
- <div class="pt-0.5">
9
- <Image src={{this.icon}} class="mr-3 h-8 w-8 flex-shrink-0 rounded-md" />
10
- </div>
11
- <div class="min-w-0 text-left">
12
- <div class="truncate text-sm font-semibold text-gray-900 hover:text-blue-600 dark:text-gray-100 dark:hover:text-blue-300">{{n-a this.name}}</div>
13
- <div class="mt-0.5 line-clamp-2 whitespace-normal text-left text-xs leading-4 text-gray-500 dark:text-gray-400">{{n-a this.description}}</div>
14
- </div>
15
- </button>
16
- </div>
1
+ {{#if this.telematic}}
2
+ {{#if this.compact}}
3
+ <button
4
+ type="button"
5
+ disabled={{and @column.permission (cannot @column.permission)}}
6
+ {{on "click" this.onClick}}
7
+ class="group flex w-full min-w-0 cursor-pointer items-center gap-2 py-0.5 text-left"
8
+ data-test-telematic-provider-compact
9
+ ...attributes
10
+ >
11
+ <Image src={{this.icon}} @fallbackSrc="/engines-dist/images/telematics/providers/default.webp" class="h-5 w-5 flex-shrink-0 rounded-md border border-gray-200 object-cover shadow-sm dark:border-gray-700" />
12
+ <div class="min-w-0 truncate text-sm leading-4 text-gray-900 group-hover:text-blue-600 dark:text-gray-200 dark:group-hover:text-blue-300">{{n-a this.name}}</div>
13
+ </button>
14
+ {{else}}
15
+ <button
16
+ type="button"
17
+ disabled={{and @column.permission (cannot @column.permission)}}
18
+ {{on "click" this.onClick}}
19
+ class="group flex w-full min-w-0 max-w-md cursor-pointer items-start gap-2 py-1 text-left"
20
+ ...attributes
21
+ >
22
+ <div class="pt-0.5">
23
+ <Image src={{this.icon}} @fallbackSrc="/engines-dist/images/telematics/providers/default.webp" class="mr-0.5 h-8 w-8 flex-shrink-0 rounded-md border border-gray-200 object-cover shadow-sm dark:border-gray-700" />
24
+ </div>
25
+ <div class="min-w-0 text-left">
26
+ <div class="truncate text-sm text-gray-900 group-hover:text-blue-600 dark:text-gray-100 dark:group-hover:text-blue-300">{{n-a this.name}}</div>
27
+ <div class="mt-0.5 max-w-[225px] truncate whitespace-nowrap text-left text-xs leading-4 text-gray-500 dark:text-gray-400">{{n-a this.description}}</div>
28
+ </div>
29
+ </button>
30
+ {{/if}}
31
+ {{else}}
32
+ <div class="py-1 text-xs text-gray-400 dark:text-gray-500" data-test-telematic-provider-empty-text ...attributes>{{this.emptyText}}</div>
33
+ {{/if}}
@@ -1,36 +1,78 @@
1
1
  import Component from '@glimmer/component';
2
- import { action } from '@ember/object';
2
+ import { action, get } from '@ember/object';
3
+
4
+ const DEFAULT_PROVIDER_ICON = '/engines-dist/images/telematics/providers/default.webp';
3
5
 
4
6
  export default class CellTelematicProviderComponent extends Component {
7
+ get telematic() {
8
+ const resourcePath = this.args.column?.resourcePath;
9
+
10
+ if (typeof resourcePath === 'function') {
11
+ const resource = resourcePath(this.args.row, this.args.value, this.args.column);
12
+
13
+ return resource ?? null;
14
+ }
15
+
16
+ if (typeof resourcePath === 'string') {
17
+ const resource = get(this.args.row, resourcePath);
18
+
19
+ return resource ?? null;
20
+ }
21
+
22
+ if (this.args.row?.telematic) {
23
+ return this.args.row.telematic;
24
+ }
25
+
26
+ if (this.args.row?.telematic_uuid || this.args.row?.telematic_name) {
27
+ return {
28
+ id: this.args.row.telematic_uuid,
29
+ name: this.args.row.telematic_name,
30
+ provider: this.args.row.provider,
31
+ provider_descriptor: this.args.row.provider_descriptor,
32
+ };
33
+ }
34
+
35
+ return this.args.row;
36
+ }
37
+
38
+ get emptyText() {
39
+ return this.args.column?.emptyText ?? '-';
40
+ }
41
+
42
+ get compact() {
43
+ return this.args.column?.compact ?? false;
44
+ }
45
+
5
46
  get descriptor() {
6
- return this.args.row?.provider_descriptor ?? {};
47
+ return this.telematic?.provider_descriptor ?? this.args.row?.provider_descriptor ?? {};
7
48
  }
8
49
 
9
50
  get name() {
10
- return this.args.row?.name ?? this.descriptor.label ?? this.args.row?.provider;
51
+ return this.telematic?.name ?? this.descriptor.label ?? this.telematic?.provider ?? this.args.row?.provider ?? this.args.row?.telematic_name;
11
52
  }
12
53
 
13
54
  get description() {
14
- return this.descriptor.description ?? this.args.row?.provider;
55
+ return this.descriptor.description ?? this.telematic?.provider ?? this.args.row?.provider;
15
56
  }
16
57
 
17
58
  get icon() {
18
- return this.descriptor.icon;
59
+ return this.descriptor.icon ?? DEFAULT_PROVIDER_ICON;
19
60
  }
20
61
 
21
62
  @action onClick(event) {
22
63
  const { row, column, onClick } = this.args;
64
+ const resource = this.telematic ?? row;
23
65
 
24
66
  if (typeof onClick === 'function') {
25
- onClick(row, event);
67
+ onClick(resource, event);
26
68
  }
27
69
 
28
70
  if (typeof column?.action === 'function') {
29
- column.action(row, event);
71
+ column.action(resource, event);
30
72
  }
31
73
 
32
74
  if (typeof column?.onClick === 'function') {
33
- column.onClick(row, event);
75
+ column.onClick(resource, event);
34
76
  }
35
77
  }
36
78
  }
@@ -0,0 +1,33 @@
1
+ {{#if this.resource}}
2
+ {{#if this.compact}}
3
+ <button
4
+ type="button"
5
+ disabled={{and @column.permission (cannot @column.permission)}}
6
+ {{on "click" this.onClick}}
7
+ class="group flex w-full min-w-0 cursor-pointer items-center gap-2 py-0.5 text-left"
8
+ data-test-vehicle-identity-compact
9
+ ...attributes
10
+ >
11
+ <div class="relative flex h-5 w-5 flex-shrink-0 items-center justify-center">
12
+ <Image src={{this.mediaUrl}} @fallbackSrc={{this.fallbackImage}} class="h-5 w-5 rounded-md border border-gray-200 object-cover shadow-sm dark:border-gray-700" />
13
+ {{#if this.hasCompactStatusDot}}
14
+ <FaIcon @icon="circle" @size="2xs" class="absolute left-0 top-0 -ml-1 -mt-1 {{this.compactStatusDotClass}}" data-test-resource-identity-status-dot />
15
+ {{/if}}
16
+ </div>
17
+ <div class="min-w-0 truncate text-sm leading-4 text-gray-900 group-hover:text-blue-600 dark:text-gray-200 dark:group-hover:text-blue-300">{{n-a this.label}}</div>
18
+ {{#if this.assignedDriverLabel}}
19
+ <span
20
+ data-test-resource-identity-meta-badge
21
+ class="inline-flex min-w-0 max-w-[12rem] flex-shrink items-center rounded-lg bg-gray-200 px-1 py-0 text-[10px] leading-4 text-gray-700 shadow-sm dark:bg-gray-800 dark:text-gray-300"
22
+ >
23
+ <FaIcon @icon="user" @prefix="fas" @size="xs" class="mr-1 flex-shrink-0" />
24
+ <span class="truncate">{{n-a this.assignedDriverLabel}}</span>
25
+ </span>
26
+ {{/if}}
27
+ </button>
28
+ {{else}}
29
+ <Table::Cell::ResourceIdentity @row={{this.resource}} @value={{@value}} @column={{this.column}} @onClick={{@onClick}} ...attributes />
30
+ {{/if}}
31
+ {{else}}
32
+ <div class="py-1 text-xs text-gray-400 dark:text-gray-500" data-test-identity-empty-text ...attributes>{{this.emptyText}}</div>
33
+ {{/if}}
@@ -0,0 +1,138 @@
1
+ import Component from '@glimmer/component';
2
+ import { action, get } from '@ember/object';
3
+ import config from 'ember-get-config';
4
+ import { resolveIdentityCellResource } from '../../utils/identity-cell-resource';
5
+
6
+ const DEFAULT_STATUS_TONES = {
7
+ available: 'text-green-500',
8
+ active: 'text-green-500',
9
+ in_service: 'text-green-500',
10
+ maintenance: 'text-yellow-500',
11
+ unavailable: 'text-gray-400',
12
+ inactive: 'text-gray-400',
13
+ out_of_service: 'text-red-500',
14
+ };
15
+
16
+ export default class CellVehicleIdentityComponent extends Component {
17
+ get resource() {
18
+ return resolveIdentityCellResource(this.args);
19
+ }
20
+
21
+ get emptyText() {
22
+ return this.args.column?.emptyText ?? '-';
23
+ }
24
+
25
+ get compact() {
26
+ return this.args.column?.compact ?? false;
27
+ }
28
+
29
+ get showStatus() {
30
+ return this.args.column?.showStatus ?? true;
31
+ }
32
+
33
+ get label() {
34
+ const vehicle = this.resource;
35
+
36
+ return get(vehicle, 'displayName') ?? get(vehicle, 'display_name') ?? get(vehicle, 'name');
37
+ }
38
+
39
+ get mediaUrl() {
40
+ return get(this.resource, 'photo_url');
41
+ }
42
+
43
+ get fallbackImage() {
44
+ return config?.defaultValues?.vehicleAvatar;
45
+ }
46
+
47
+ get hasCompactStatusDot() {
48
+ return this.args.column?.showStatusDot ?? this.args.column?.showOnlineIndicator ?? true;
49
+ }
50
+
51
+ get compactStatusValue() {
52
+ const vehicle = this.resource;
53
+
54
+ return get(vehicle, 'online') ?? get(vehicle, 'status');
55
+ }
56
+
57
+ get compactStatusToneClass() {
58
+ const value = this.compactStatusValue;
59
+ const statusToneMap = {
60
+ ...DEFAULT_STATUS_TONES,
61
+ ...(this.args.column?.statusToneMap ?? {}),
62
+ };
63
+
64
+ if (typeof this.args.column?.statusToneClass === 'function') {
65
+ return this.args.column.statusToneClass(value, this.resource, this.args.column);
66
+ }
67
+
68
+ if (typeof value === 'boolean') {
69
+ return value ? 'text-green-500' : 'text-yellow-200';
70
+ }
71
+
72
+ return statusToneMap[value] ?? statusToneMap[String(value ?? '').toLowerCase()] ?? 'text-gray-400';
73
+ }
74
+
75
+ get compactStatusDotClass() {
76
+ return this.compactStatusToneClass;
77
+ }
78
+
79
+ get assignedDriverLabel() {
80
+ const vehicle = this.resource;
81
+
82
+ return get(vehicle, 'driver.displayName') ?? get(vehicle, 'driver.display_name') ?? get(vehicle, 'driver.name') ?? get(vehicle, 'driver_name');
83
+ }
84
+
85
+ get column() {
86
+ return {
87
+ ...(this.args.column ?? {}),
88
+ labelPath: (vehicle) => get(vehicle, 'displayName') ?? get(vehicle, 'display_name') ?? get(vehicle, 'name'),
89
+ mediaPath: 'photo_url',
90
+ fallbackImage: config?.defaultValues?.vehicleAvatar,
91
+ statusPath: this.showStatus ? 'status' : undefined,
92
+ onlinePath: 'online',
93
+ showStatusBadge: this.showStatus ? (this.args.column?.showStatusBadge ?? true) : false,
94
+ statusBadgeSize: this.args.column?.statusBadgeSize ?? 'xxs',
95
+ statusBadgeWrapperClass: this.args.column?.statusBadgeWrapperClass ?? 'resource-identity-status-badge vehicle-identity-status-badge',
96
+ metaPaths: [
97
+ {
98
+ value: (vehicle) => get(vehicle, 'plate_number') ?? get(vehicle, 'call_sign') ?? get(vehicle, 'vehicle_number') ?? get(vehicle, 'public_id'),
99
+ icon: 'id-card',
100
+ style: 'badge',
101
+ class: 'max-w-[12rem]',
102
+ },
103
+ {
104
+ value: (vehicle) => get(vehicle, 'driver.displayName') ?? get(vehicle, 'driver.display_name') ?? get(vehicle, 'driver.name') ?? get(vehicle, 'driver_name'),
105
+ icon: 'user',
106
+ style: 'badge',
107
+ class: 'max-w-[12rem]',
108
+ },
109
+ ],
110
+ statusToneMap: {
111
+ available: 'text-green-500',
112
+ active: 'text-green-500',
113
+ in_service: 'text-green-500',
114
+ maintenance: 'text-yellow-500',
115
+ unavailable: 'text-gray-400',
116
+ inactive: 'text-gray-400',
117
+ out_of_service: 'text-red-500',
118
+ },
119
+ };
120
+ }
121
+
122
+ @action onClick(event) {
123
+ const { column, onClick } = this.args;
124
+ const resource = this.resource;
125
+
126
+ if (typeof onClick === 'function') {
127
+ onClick(resource, event);
128
+ }
129
+
130
+ if (typeof column?.onClick === 'function') {
131
+ column.onClick(resource, event);
132
+ }
133
+
134
+ if (typeof column?.action === 'function') {
135
+ column.action(resource, event);
136
+ }
137
+ }
138
+ }
@@ -1,154 +1,125 @@
1
- <div class="details-wrapper" ...attributes>
2
- <ContentPanel @title="Integration" @open={{true}} @wrapperClass="bordered-top">
3
- <div class="grid grid-cols-1 lg:grid-cols-3 gap-2">
4
- <div class="field-info-container lg:col-span-3">
5
- <div class="field-name">{{t "device.fields.telematic"}}</div>
6
- <div class="field-value">
7
- {{#if @resource.telematic}}
8
- <div class="flex flex-row space-x-2 text-sm">
9
- <div class="pt-1.5">
10
- <Image src={{@resource.telematic.provider_descriptor.icon}} class="w-5 h-5" />
11
- </div>
12
- <div>
13
- <div class="font-semibold">{{@resource.telematic.provider_descriptor.label}}</div>
14
- <div class="text-xs text-gray-400 dark:text-gray-500">
15
- {{n-a @resource.telematic.provider_descriptor.description}}
16
- </div>
17
- </div>
18
- </div>
19
- {{else}}
20
- {{n-a null}}
21
- {{/if}}
1
+ <div class="flex flex-col gap-3 p-3" ...attributes>
2
+ <section class="overflow-hidden rounded-md border border-gray-200 bg-white shadow-sm dark:border-gray-700 dark:bg-gray-800">
3
+ <div class="flex flex-col gap-3 p-3 lg:flex-row lg:items-start lg:justify-between">
4
+ <div class="min-w-0">
5
+ <div class="flex flex-wrap items-center gap-2">
6
+ <h2 class="truncate text-base font-bold text-gray-900 dark:text-gray-100">{{this.displayName}}</h2>
7
+ <Badge @status={{this.connectionTone}}>{{smart-humanize this.connectionLabel}}</Badge>
8
+ <Badge @status={{this.attachmentTone}}>{{this.attachmentLabel}}</Badge>
9
+ </div>
10
+ <div class="mt-2 flex flex-wrap gap-2 text-xs text-gray-500 dark:text-gray-300">
11
+ <span>Last seen: {{n-a (format-date-fns this.lastSeenLabel "dd MMM yyyy, HH:mm")}}</span>
12
+ <span>Provider: {{n-a (titleize this.providerLabel)}}</span>
13
+ <span>Signal: {{n-a this.signalLabel}}</span>
14
+ <span>Device ID: {{n-a @resource.device_id}}</span>
22
15
  </div>
23
16
  </div>
24
17
 
25
- <div class="field-info-container">
26
- <div class="field-name">{{t "device.fields.data-frequency"}}</div>
27
- <div class="field-value">{{n-a @resource.data_frequency}}</div>
28
- </div>
29
- </div>
30
- </ContentPanel>
31
-
32
- <ContentPanel @title="Identity" @open={{true}} @wrapperClass="bordered-top">
33
- <div class="grid grid-cols-1 lg:grid-cols-3 gap-2">
34
- <div class="field-info-container">
35
- <div class="field-name">{{t "device.fields.device-name"}}</div>
36
- <div class="field-value">{{n-a @resource.name}}</div>
37
- </div>
38
-
39
- <div class="field-info-container">
40
- <div class="field-name">{{t "device.fields.device-type"}}</div>
41
- <div class="field-value">{{n-a (get-fleet-ops-option-label "deviceTypes" @resource.type)}}</div>
42
- </div>
43
-
44
- <div class="field-info-container">
45
- <div class="field-name">{{t "device.fields.device-id"}}</div>
46
- <div class="field-value">{{n-a @resource.device_id}}</div>
47
- </div>
48
-
49
- <div class="field-info-container">
50
- <div class="field-name">Internal ID</div>
51
- <div class="field-value">{{n-a @resource.internal_id}}</div>
52
- </div>
18
+ {{#if this.providerIcon}}
19
+ <div class="flex flex-shrink-0 items-center gap-2 rounded-md border border-gray-100 bg-gray-50 px-3 py-2 dark:border-gray-700 dark:bg-gray-800/70">
20
+ <Image src={{this.providerIcon}} class="h-6 w-6 rounded" />
21
+ <span class="text-xs font-semibold text-gray-700 dark:text-gray-200">{{n-a this.providerLabel}}</span>
22
+ </div>
23
+ {{/if}}
53
24
  </div>
54
- </ContentPanel>
55
-
56
- <ContentPanel @title="Hardware" @open={{true}} @wrapperClass="bordered-top">
57
- <div class="grid grid-cols-1 lg:grid-cols-3 gap-2">
58
- <div class="field-info-container">
59
- <div class="field-name">{{t "device.fields.device-provider"}}</div>
60
- <div class="field-value">{{n-a (titleize @resource.provider)}}</div>
61
- </div>
62
-
63
- <div class="field-info-container">
64
- <div class="field-name">{{t "device.fields.device-model"}}</div>
65
- <div class="field-value">{{n-a @resource.model}}</div>
25
+ </section>
26
+
27
+ <section class="grid grid-cols-1 gap-2 sm:grid-cols-2 lg:grid-cols-4">
28
+ {{#each this.metrics as |metric|}}
29
+ <div class="fleet-ops-widget fleet-ops-kpi-tile fleetops-connectivity-kpi-tile {{metric.accentClass}} relative min-h-[82px] overflow-hidden rounded-md border bg-white shadow-sm dark:bg-gray-800">
30
+ <div class="flex h-full items-start justify-between gap-2 p-3">
31
+ <div class="min-w-0">
32
+ <div class="truncate text-[10px] font-bold uppercase text-gray-500 dark:text-gray-400">{{metric.label}}</div>
33
+ <div class="mt-1 truncate text-lg font-extrabold leading-none text-gray-900 dark:text-gray-50">{{metric.value}}</div>
34
+ {{#if metric.meta}}
35
+ <div class="mt-1 truncate text-xs text-gray-500 dark:text-gray-300">{{metric.meta}}</div>
36
+ {{/if}}
37
+ </div>
38
+ <div class="fleetops-connectivity-kpi-icon flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-md text-[13px]">
39
+ <FaIcon @icon={{metric.icon}} />
40
+ </div>
41
+ </div>
66
42
  </div>
43
+ {{/each}}
44
+ </section>
67
45
 
68
- <div class="field-info-container">
69
- <div class="field-name">{{t "device.fields.manufacturer"}}</div>
70
- <div class="field-value">{{n-a @resource.manufacturer}}</div>
46
+ <section class="grid grid-cols-1 gap-3 xl:grid-cols-2">
47
+ <div class="overflow-hidden rounded-md border border-gray-200 bg-white shadow-sm dark:border-gray-700 dark:bg-gray-800">
48
+ <div class="border-b border-gray-200 px-3 py-2 dark:border-gray-700">
49
+ <h3 class="text-sm font-bold text-gray-900 dark:text-gray-100">Operational Snapshot</h3>
71
50
  </div>
72
-
73
- <div class="field-info-container lg:col-span-3">
74
- <div class="field-name">{{t "device.fields.serial-number"}}</div>
75
- <div class="field-value">{{n-a @resource.serial_number}}</div>
51
+ <div class="grid grid-cols-1 gap-2 p-3 sm:grid-cols-2">
52
+ <div class="field-info-container">
53
+ <div class="field-name">Location</div>
54
+ <div class="field-value">{{n-a (or @resource.location this.lastPositionLabel)}}</div>
55
+ </div>
56
+ <div class="field-info-container">
57
+ <div class="field-name">{{t "device.fields.data-frequency"}}</div>
58
+ <div class="field-value">{{n-a @resource.data_frequency}}</div>
59
+ </div>
60
+ <div class="field-info-container">
61
+ <div class="field-name">Firmware</div>
62
+ <div class="field-value">{{n-a this.firmwareLabel}}</div>
63
+ </div>
64
+ <div class="field-info-container">
65
+ <div class="field-name">{{t "device.fields.installation-date"}}</div>
66
+ <div class="field-value">{{n-a (format-date @resource.installation_date)}}</div>
67
+ </div>
68
+ <div class="field-info-container">
69
+ <div class="field-name">{{t "device.fields.last-maintenance-date"}}</div>
70
+ <div class="field-value">{{n-a (format-date @resource.last_maintenance_date)}}</div>
71
+ </div>
72
+ <div class="field-info-container">
73
+ <div class="field-name">{{t "device.fields.status"}}</div>
74
+ <div class="field-value"><Badge @status={{@resource.status}}>{{smart-humanize @resource.status}}</Badge></div>
75
+ </div>
76
76
  </div>
77
77
  </div>
78
- </ContentPanel>
79
78
 
80
- <ContentPanel @title="Installation & Maintenance" @open={{true}} @wrapperClass="bordered-top">
81
- <div class="grid grid-cols-1 lg:grid-cols-3 gap-2">
82
- <div class="field-info-container lg:col-span-2">
83
- <div class="field-name">{{t "device.fields.device-location"}}</div>
84
- <div class="field-value">{{n-a @resource.location}}</div>
79
+ <div class="overflow-hidden rounded-md border border-gray-200 bg-white shadow-sm dark:border-gray-700 dark:bg-gray-800">
80
+ <div class="border-b border-gray-200 px-3 py-2 dark:border-gray-700">
81
+ <h3 class="text-sm font-bold text-gray-900 dark:text-gray-100">Critical Details</h3>
85
82
  </div>
86
-
87
- <div class="field-info-container">
88
- <div class="field-name">{{t "device.fields.installation-date"}}</div>
89
- <div class="field-value">{{or (format-date @resource.installation_date) "-"}}</div>
90
- </div>
91
-
92
- <div class="field-info-container">
93
- <div class="field-name">{{t "device.fields.last-maintenance-date"}}</div>
94
- <div class="field-value">{{or (format-date @resource.last_maintenance_date) "-"}}</div>
95
- </div>
96
- </div>
97
- </ContentPanel>
98
-
99
- <ContentPanel @title="Connectivity" @open={{true}} @wrapperClass="bordered-top">
100
- <div class="grid grid-cols-1 lg:grid-cols-3 gap-2">
101
- <div class="field-info-container">
102
- <div class="field-name">{{t "common.online"}}</div>
103
- <div class="field-value">
104
- {{#if @resource.is_online}}
105
- <Badge @status="online">{{t "common.online"}}</Badge>
106
- {{else}}
107
- <Badge @status="offline">{{t "common.offline"}}</Badge>
108
- {{/if}}
83
+ <div class="grid grid-cols-1 gap-2 p-3 sm:grid-cols-2">
84
+ <div class="field-info-container">
85
+ <div class="field-name">{{t "device.fields.device-type"}}</div>
86
+ <div class="field-value">{{n-a (get-fleet-ops-option-label "deviceTypes" @resource.type)}}</div>
109
87
  </div>
110
- </div>
111
-
112
- <div class="field-info-container">
113
- <div class="field-name">{{t "common.last-seen-at"}}</div>
114
- <div class="field-value">{{or (format-date @resource.last_online_at) "-"}}</div>
115
- </div>
116
-
117
- {{#if @resource.signal_strength}}
118
88
  <div class="field-info-container">
119
- <div class="field-name">{{t "device.fields.signal-strength"}}</div>
120
- <div class="field-value">{{n-a @resource.signal_strength}}</div>
89
+ <div class="field-name">{{t "device.fields.device-model"}}</div>
90
+ <div class="field-value">{{n-a @resource.model}}</div>
121
91
  </div>
122
- {{/if}}
123
- </div>
124
- </ContentPanel>
125
-
126
- <ContentPanel @title="Warranty & Status" @open={{true}} @wrapperClass="bordered-top">
127
- <div class="grid grid-cols-1 lg:grid-cols-3 gap-2">
128
- <div class="field-info-container">
129
- <div class="field-name">{{t "device.fields.status"}}</div>
130
- <div class="field-value">
131
- <Badge @status={{@resource.status}}>
132
- {{smart-humanize @resource.status}}
133
- </Badge>
92
+ <div class="field-info-container">
93
+ <div class="field-name">{{t "device.fields.manufacturer"}}</div>
94
+ <div class="field-value">{{n-a @resource.manufacturer}}</div>
95
+ </div>
96
+ <div class="field-info-container">
97
+ <div class="field-name">{{t "device.fields.serial-number"}}</div>
98
+ <div class="field-value">{{n-a @resource.serial_number}}</div>
99
+ </div>
100
+ <div class="field-info-container">
101
+ <div class="field-name">IMEI</div>
102
+ <div class="field-value">{{n-a @resource.imei}}</div>
103
+ </div>
104
+ <div class="field-info-container">
105
+ <div class="field-name">IMSI</div>
106
+ <div class="field-value">{{n-a @resource.imsi}}</div>
107
+ </div>
108
+ <div class="field-info-container">
109
+ <div class="field-name">Internal ID</div>
110
+ <div class="field-value">{{n-a @resource.internal_id}}</div>
111
+ </div>
112
+ <div class="field-info-container">
113
+ <div class="field-name">{{t "device.fields.warranty"}}</div>
114
+ <div class="field-value">{{n-a (or @resource.warranty.name @resource.warranty_name)}}</div>
115
+ </div>
116
+ <div class="field-info-container sm:col-span-2">
117
+ <div class="field-name">{{t "device.fields.notes"}}</div>
118
+ <div class="field-value whitespace-pre-line">{{n-a @resource.notes}}</div>
134
119
  </div>
135
- </div>
136
-
137
- <div class="field-info-container lg:col-span-2">
138
- <div class="field-name">{{t "device.fields.warranty"}}</div>
139
- <div class="field-value">{{n-a @resource.warranty.name}}</div>
140
- </div>
141
- </div>
142
- </ContentPanel>
143
-
144
- <ContentPanel @title="Notes" @open={{true}} @wrapperClass="bordered-top">
145
- <div class="grid grid-cols-1 lg:grid-cols-1 gap-2">
146
- <div class="field-info-container">
147
- <div class="field-name">{{t "device.fields.notes"}}</div>
148
- <div class="field-value">{{n-a @resource.notes}}</div>
149
120
  </div>
150
121
  </div>
151
- </ContentPanel>
122
+ </section>
152
123
 
153
124
  <CustomField::Yield @subject={{@resource}} @viewMode={{true}} @wrapperClass="bordered-top" />
154
- </div>
125
+ </div>