@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
@@ -19,6 +19,7 @@
19
19
  <div class="fleetbase-model-select fleetbase-power-select ember-model-select">
20
20
  <PowerSelectMultiple
21
21
  @searchEnabled={{true}}
22
+ @searchField="label"
22
23
  @options={{this.trackingProviderOptions}}
23
24
  @selected={{this.selectedTrackingFallbackOptions}}
24
25
  @onChange={{this.setTrackingFallbacks}}
@@ -79,4 +80,4 @@
79
80
  @isLoading={{or this.saveSettings.isRunning this.loadSettings.isRunning}}
80
81
  @disabled={{this.saveSettings.isRunning}}
81
82
  />
82
- </EmberWormhole>
83
+ </EmberWormhole>
@@ -0,0 +1,10 @@
1
+ <div ...attributes>
2
+ {{#if this.hasVehicle}}
3
+ <Cell::VehicleIdentity @row={{this.vehicleResource}} @column={{this.identityColumn}} @onClick={{this.onClick}} />
4
+ {{else}}
5
+ <div class="flex items-center gap-2 py-1 text-xs text-gray-400 dark:text-gray-500">
6
+ <FaIcon @icon="link-slash" />
7
+ <span>Unattached</span>
8
+ </div>
9
+ {{/if}}
10
+ </div>
@@ -0,0 +1,58 @@
1
+ import Component from '@glimmer/component';
2
+ import { action } from '@ember/object';
3
+
4
+ export default class CellAttachedVehicleComponent extends Component {
5
+ get device() {
6
+ return this.args.row;
7
+ }
8
+
9
+ get vehicle() {
10
+ return this.device?.attachable;
11
+ }
12
+
13
+ get vehicleResource() {
14
+ return (
15
+ this.vehicle ?? {
16
+ id: this.device?.attachable_uuid,
17
+ displayName: this.device?.attached_to_name,
18
+ display_name: this.device?.attached_to_name,
19
+ name: this.device?.attached_to_name,
20
+ public_id: this.device?.attachable_uuid,
21
+ }
22
+ );
23
+ }
24
+
25
+ get identityColumn() {
26
+ return {
27
+ ...(this.args.column ?? {}),
28
+ action: null,
29
+ showStatusBadge: false,
30
+ };
31
+ }
32
+
33
+ get hasVehicle() {
34
+ return Boolean(this.device?.attachable_uuid && this.isVehicleAttachment);
35
+ }
36
+
37
+ get isVehicleAttachment() {
38
+ const attachableType = `${this.device?.attachable_type ?? ''}`.toLowerCase();
39
+
40
+ return !attachableType || attachableType.includes('vehicle');
41
+ }
42
+
43
+ @action onClick(_vehicle, event) {
44
+ const { column, onClick } = this.args;
45
+
46
+ if (!this.hasVehicle) {
47
+ return;
48
+ }
49
+
50
+ if (typeof onClick === 'function') {
51
+ onClick(this.device, event);
52
+ }
53
+
54
+ if (typeof column?.action === 'function') {
55
+ column.action(this.device, event);
56
+ }
57
+ }
58
+ }
@@ -0,0 +1,24 @@
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-device-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
+ </button>
19
+ {{else}}
20
+ <Table::Cell::ResourceIdentity @row={{this.resource}} @value={{@value}} @column={{this.column}} @onClick={{@onClick}} ...attributes />
21
+ {{/if}}
22
+ {{else}}
23
+ <div class="py-1 text-xs text-gray-400 dark:text-gray-500" data-test-identity-empty-text ...attributes>{{this.emptyText}}</div>
24
+ {{/if}}
@@ -0,0 +1,129 @@
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
+ online: 'text-green-500',
8
+ active: 'text-green-500',
9
+ recently_offline: 'text-yellow-500',
10
+ offline: 'text-gray-400',
11
+ long_offline: 'text-gray-400',
12
+ never_connected: 'text-gray-400',
13
+ inactive: 'text-gray-400',
14
+ error: 'text-red-500',
15
+ };
16
+
17
+ export default class CellDeviceIdentityComponent extends Component {
18
+ get resource() {
19
+ return resolveIdentityCellResource(this.args);
20
+ }
21
+
22
+ get emptyText() {
23
+ return this.args.column?.emptyText ?? '-';
24
+ }
25
+
26
+ get showStatus() {
27
+ return this.args.column?.showStatus ?? true;
28
+ }
29
+
30
+ get compact() {
31
+ return this.args.column?.compact ?? false;
32
+ }
33
+
34
+ get label() {
35
+ const device = this.resource;
36
+
37
+ return get(device, 'displayName') ?? get(device, 'display_name') ?? get(device, 'name') ?? get(device, 'device_id') ?? get(device, 'imei') ?? get(device, 'serial_number');
38
+ }
39
+
40
+ get mediaUrl() {
41
+ return get(this.resource, 'photo_url');
42
+ }
43
+
44
+ get fallbackImage() {
45
+ return config?.defaultValues?.placeholderImage;
46
+ }
47
+
48
+ get hasCompactStatusDot() {
49
+ return this.args.column?.showStatusDot ?? this.args.column?.showOnlineIndicator ?? true;
50
+ }
51
+
52
+ get compactStatusValue() {
53
+ const device = this.resource;
54
+
55
+ return get(device, 'is_online') ?? get(device, 'connection_status') ?? get(device, 'status');
56
+ }
57
+
58
+ get compactStatusToneClass() {
59
+ const value = this.compactStatusValue;
60
+ const statusToneMap = {
61
+ ...DEFAULT_STATUS_TONES,
62
+ ...(this.args.column?.statusToneMap ?? {}),
63
+ };
64
+
65
+ if (typeof this.args.column?.statusToneClass === 'function') {
66
+ return this.args.column.statusToneClass(value, this.resource, this.args.column);
67
+ }
68
+
69
+ if (typeof value === 'boolean') {
70
+ return value ? 'text-green-500' : 'text-yellow-200';
71
+ }
72
+
73
+ return statusToneMap[value] ?? statusToneMap[String(value ?? '').toLowerCase()] ?? 'text-gray-400';
74
+ }
75
+
76
+ get compactStatusDotClass() {
77
+ return this.compactStatusToneClass;
78
+ }
79
+
80
+ get column() {
81
+ return {
82
+ ...(this.args.column ?? {}),
83
+ labelPath: (device) =>
84
+ get(device, 'displayName') ?? get(device, 'display_name') ?? get(device, 'name') ?? get(device, 'device_id') ?? get(device, 'imei') ?? get(device, 'serial_number'),
85
+ mediaPath: 'photo_url',
86
+ fallbackImage: config?.defaultValues?.placeholderImage,
87
+ statusPath: this.showStatus ? (device) => get(device, 'connection_status') ?? get(device, 'status') : undefined,
88
+ onlinePath: 'is_online',
89
+ showStatusBadge: this.showStatus ? (this.args.column?.showStatusBadge ?? true) : false,
90
+ statusBadgeSize: this.args.column?.statusBadgeSize ?? 'xxs',
91
+ statusBadgeWrapperClass: this.args.column?.statusBadgeWrapperClass ?? 'resource-identity-status-badge device-identity-status-badge',
92
+ metaPaths: [
93
+ {
94
+ value: (device) => get(device, 'imei') ?? get(device, 'device_id') ?? get(device, 'ident') ?? get(device, 'serial_number'),
95
+ icon: 'microchip',
96
+ style: 'badge',
97
+ class: 'max-w-[12rem]',
98
+ },
99
+ ],
100
+ statusToneMap: {
101
+ online: 'text-green-500',
102
+ active: 'text-green-500',
103
+ recently_offline: 'text-yellow-500',
104
+ offline: 'text-gray-400',
105
+ long_offline: 'text-gray-400',
106
+ never_connected: 'text-gray-400',
107
+ inactive: 'text-gray-400',
108
+ error: 'text-red-500',
109
+ },
110
+ };
111
+ }
112
+
113
+ @action onClick(event) {
114
+ const { column, onClick } = this.args;
115
+ const resource = this.resource;
116
+
117
+ if (typeof onClick === 'function') {
118
+ onClick(resource, event);
119
+ }
120
+
121
+ if (typeof column?.onClick === 'function') {
122
+ column.onClick(resource, event);
123
+ }
124
+
125
+ if (typeof column?.action === 'function') {
126
+ column.action(resource, event);
127
+ }
128
+ }
129
+ }
@@ -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-driver-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.assignedVehicleLabel}}
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="car" @prefix="fas" @size="xs" class="mr-1 flex-shrink-0" />
24
+ <span class="truncate">{{n-a this.assignedVehicleLabel}}</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,143 @@
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
+ on_duty: 'text-green-500',
10
+ busy: 'text-yellow-500',
11
+ assigned: 'text-yellow-500',
12
+ unavailable: 'text-gray-400',
13
+ offline: 'text-gray-400',
14
+ suspended: 'text-red-500',
15
+ };
16
+
17
+ export default class CellDriverIdentityComponent extends Component {
18
+ get resource() {
19
+ return resolveIdentityCellResource(this.args);
20
+ }
21
+
22
+ get emptyText() {
23
+ return this.args.column?.emptyText ?? '-';
24
+ }
25
+
26
+ get compact() {
27
+ return this.args.column?.compact ?? false;
28
+ }
29
+
30
+ get label() {
31
+ const driver = this.resource;
32
+
33
+ return get(driver, 'name') ?? get(driver, 'displayName') ?? get(driver, 'display_name');
34
+ }
35
+
36
+ get mediaUrl() {
37
+ return get(this.resource, 'photo_url');
38
+ }
39
+
40
+ get fallbackImage() {
41
+ return config?.defaultValues?.driverImage;
42
+ }
43
+
44
+ get hasCompactStatusDot() {
45
+ return this.args.column?.showStatusDot ?? this.args.column?.showOnlineIndicator ?? true;
46
+ }
47
+
48
+ get compactStatusValue() {
49
+ const driver = this.resource;
50
+
51
+ return get(driver, 'online') ?? get(driver, 'status');
52
+ }
53
+
54
+ get compactStatusToneClass() {
55
+ const value = this.compactStatusValue;
56
+ const statusToneMap = {
57
+ ...DEFAULT_STATUS_TONES,
58
+ ...(this.args.column?.statusToneMap ?? {}),
59
+ };
60
+
61
+ if (typeof this.args.column?.statusToneClass === 'function') {
62
+ return this.args.column.statusToneClass(value, this.resource, this.args.column);
63
+ }
64
+
65
+ if (typeof value === 'boolean') {
66
+ return value ? 'text-green-500' : 'text-yellow-200';
67
+ }
68
+
69
+ return statusToneMap[value] ?? statusToneMap[String(value ?? '').toLowerCase()] ?? 'text-gray-400';
70
+ }
71
+
72
+ get compactStatusDotClass() {
73
+ return this.compactStatusToneClass;
74
+ }
75
+
76
+ get assignedVehicleLabel() {
77
+ const column = this.args.column ?? {};
78
+ const driver = this.resource;
79
+
80
+ if (typeof column.assignedVehicleLabel === 'function') {
81
+ return column.assignedVehicleLabel(driver, this.args.row, column);
82
+ }
83
+
84
+ if (column.assignedVehicleLabel !== undefined) {
85
+ return column.assignedVehicleLabel;
86
+ }
87
+
88
+ if (typeof column.assignedVehiclePath === 'string') {
89
+ return get(this.args.row, column.assignedVehiclePath) ?? get(driver, column.assignedVehiclePath);
90
+ }
91
+
92
+ return get(driver, 'vehicle_assigned.display_name') ?? get(driver, 'vehicle.display_name') ?? get(driver, 'vehicle_name');
93
+ }
94
+
95
+ get column() {
96
+ return {
97
+ ...(this.args.column ?? {}),
98
+ labelPath: 'name',
99
+ mediaPath: 'photo_url',
100
+ fallbackImage: config?.defaultValues?.driverImage,
101
+ statusPath: 'status',
102
+ onlinePath: 'online',
103
+ showStatusBadge: this.args.column?.showStatusBadge ?? true,
104
+ statusBadgeSize: this.args.column?.statusBadgeSize ?? 'xxs',
105
+ statusBadgeWrapperClass: this.args.column?.statusBadgeWrapperClass ?? 'resource-identity-status-badge driver-identity-status-badge order-first',
106
+ metaPaths: [
107
+ {
108
+ value: (driver) => get(driver, 'vehicle_assigned.display_name') ?? get(driver, 'vehicle.display_name') ?? get(driver, 'vehicle_name'),
109
+ icon: 'car',
110
+ style: 'badge',
111
+ class: 'max-w-[12rem]',
112
+ },
113
+ ],
114
+ statusToneMap: {
115
+ available: 'text-green-500',
116
+ active: 'text-green-500',
117
+ on_duty: 'text-green-500',
118
+ busy: 'text-yellow-500',
119
+ assigned: 'text-yellow-500',
120
+ unavailable: 'text-gray-400',
121
+ offline: 'text-gray-400',
122
+ suspended: 'text-red-500',
123
+ },
124
+ };
125
+ }
126
+
127
+ @action onClick(event) {
128
+ const { column, onClick } = this.args;
129
+ const resource = this.resource;
130
+
131
+ if (typeof onClick === 'function') {
132
+ onClick(resource, event);
133
+ }
134
+
135
+ if (typeof column?.onClick === 'function') {
136
+ column.onClick(resource, event);
137
+ }
138
+
139
+ if (typeof column?.action === 'function') {
140
+ column.action(resource, event);
141
+ }
142
+ }
143
+ }
@@ -0,0 +1,5 @@
1
+ {{#if this.resource}}
2
+ <Table::Cell::ResourceIdentity @row={{this.resource}} @value={{@value}} @column={{this.column}} @onClick={{@onClick}} ...attributes />
3
+ {{else}}
4
+ <div class="py-1 text-xs text-gray-400 dark:text-gray-500" data-test-identity-empty-text ...attributes>{{this.emptyText}}</div>
5
+ {{/if}}
@@ -0,0 +1,48 @@
1
+ import Component from '@glimmer/component';
2
+ import { get } from '@ember/object';
3
+ import config from 'ember-get-config';
4
+ import { resolveIdentityCellResource } from '../../utils/identity-cell-resource';
5
+
6
+ export default class CellEquipmentIdentityComponent extends Component {
7
+ get resource() {
8
+ return resolveIdentityCellResource(this.args);
9
+ }
10
+
11
+ get emptyText() {
12
+ return this.args.column?.emptyText ?? '-';
13
+ }
14
+
15
+ get column() {
16
+ return {
17
+ ...(this.args.column ?? {}),
18
+ labelPath: 'name',
19
+ mediaPath: 'photo_url',
20
+ fallbackImage: config?.defaultValues?.equipmentImage ?? config?.defaultValues?.placeholderImage,
21
+ statusPath: (equipment) => (get(equipment, 'is_equipped') ? 'equipped' : (get(equipment, 'status') ?? 'unequipped')),
22
+ statusFormatter: () => null,
23
+ metaPaths: [
24
+ {
25
+ value: (equipment) => get(equipment, 'type'),
26
+ formatter: (type) => type,
27
+ icon: 'toolbox',
28
+ style: 'badge',
29
+ },
30
+ {
31
+ value: (equipment) => get(equipment, 'serial_number') ?? get(equipment, 'code') ?? get(equipment, 'public_id'),
32
+ icon: 'barcode',
33
+ style: 'badge',
34
+ class: 'max-w-[12rem]',
35
+ },
36
+ ],
37
+ statusToneMap: {
38
+ active: 'text-green-500',
39
+ equipped: 'text-green-500',
40
+ available: 'text-green-500',
41
+ maintenance: 'text-yellow-500',
42
+ unequipped: 'text-gray-400',
43
+ inactive: 'text-gray-400',
44
+ retired: 'text-red-500',
45
+ },
46
+ };
47
+ }
48
+ }
@@ -0,0 +1,5 @@
1
+ {{#if this.resource}}
2
+ <Table::Cell::ResourceIdentity @row={{this.resource}} @value={{@value}} @column={{this.column}} @onClick={{@onClick}} ...attributes />
3
+ {{else}}
4
+ <div class="py-1 text-xs text-gray-400 dark:text-gray-500" data-test-identity-empty-text ...attributes>{{this.emptyText}}</div>
5
+ {{/if}}
@@ -0,0 +1,63 @@
1
+ import Component from '@glimmer/component';
2
+ import { get } from '@ember/object';
3
+ import config from 'ember-get-config';
4
+ import { resolveIdentityCellResource } from '../../utils/identity-cell-resource';
5
+
6
+ function inventoryStatus(part) {
7
+ if (get(part, 'is_low_stock')) {
8
+ return 'low_stock';
9
+ }
10
+
11
+ if (get(part, 'is_in_stock')) {
12
+ return 'in_stock';
13
+ }
14
+
15
+ return 'out_of_stock';
16
+ }
17
+
18
+ function inventoryStatusLabel(part) {
19
+ return inventoryStatus(part)
20
+ .split('_')
21
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
22
+ .join(' ');
23
+ }
24
+
25
+ export default class CellPartIdentityComponent extends Component {
26
+ get resource() {
27
+ return resolveIdentityCellResource(this.args);
28
+ }
29
+
30
+ get emptyText() {
31
+ return this.args.column?.emptyText ?? '-';
32
+ }
33
+
34
+ get column() {
35
+ return {
36
+ ...(this.args.column ?? {}),
37
+ labelPath: 'name',
38
+ mediaPath: 'photo_url',
39
+ fallbackImage: config?.defaultValues?.partImage ?? config?.defaultValues?.placeholderImage,
40
+ statusPath: inventoryStatus,
41
+ statusFormatter: () => null,
42
+ metaPaths: [
43
+ {
44
+ value: (part) => get(part, 'type'),
45
+ icon: 'tag',
46
+ style: 'badge',
47
+ },
48
+ {
49
+ value: inventoryStatusLabel,
50
+ icon: 'boxes-stacked',
51
+ style: 'badge',
52
+ },
53
+ ],
54
+ statusToneMap: {
55
+ in_stock: 'text-green-500',
56
+ low_stock: 'text-yellow-500',
57
+ out_of_stock: 'text-red-500',
58
+ active: 'text-green-500',
59
+ inactive: 'text-gray-400',
60
+ },
61
+ };
62
+ }
63
+ }
@@ -0,0 +1,38 @@
1
+ <div ...attributes>
2
+ <button
3
+ type="button"
4
+ disabled={{and @column.permission (cannot @column.permission)}}
5
+ {{on "click" this.onClick}}
6
+ class="group flex w-full min-w-0 max-w-md cursor-pointer items-center gap-2 py-0.5 text-left"
7
+ >
8
+ <div class="relative flex h-9 w-9 flex-shrink-0 items-center justify-center">
9
+ <Image
10
+ src={{this.imageUrl}}
11
+ @fallbackSrc={{config "defaultValues.placeholderImage"}}
12
+ alt={{this.name}}
13
+ data-test-telematic-device-image
14
+ class="h-9 w-9 rounded-sm border border-gray-200 bg-white object-cover shadow-sm dark:border-gray-700 dark:bg-gray-900"
15
+ />
16
+ <FaIcon
17
+ @icon="circle"
18
+ @size="2xs"
19
+ data-test-telematic-device-online-indicator
20
+ class="absolute left-0 top-0 -ml-1 -mt-1 h-2 w-2 {{if this.isOnline 'text-green-500' 'text-yellow-200'}}"
21
+ />
22
+ </div>
23
+ <div class="min-w-0 text-left">
24
+ <div class="truncate text-sm font-semibold leading-4 text-gray-900 group-hover:text-blue-600 dark:text-gray-100 dark:group-hover:text-blue-300">{{n-a this.name}}</div>
25
+ <div class="flex min-w-0 flex-wrap items-center gap-x-2 gap-y-0.5 text-xs leading-4 text-gray-500 dark:text-gray-400">
26
+ <Badge
27
+ @status={{this.connectionStatus}}
28
+ @hideStatusDot={{true}}
29
+ @wrapperClass="fleetops-device-status-badge"
30
+ data-test-telematic-device-status-badge
31
+ >
32
+ {{smart-humanize this.connectionStatus}}
33
+ </Badge>
34
+ <span class="truncate" data-test-telematic-device-identifier>{{n-a this.identifier}}</span>
35
+ </div>
36
+ </div>
37
+ </button>
38
+ </div>
@@ -0,0 +1,44 @@
1
+ import Component from '@glimmer/component';
2
+ import { action } from '@ember/object';
3
+
4
+ export default class CellTelematicDeviceComponent extends Component {
5
+ get device() {
6
+ return this.args.row;
7
+ }
8
+
9
+ get name() {
10
+ return this.device?.displayName ?? this.device?.display_name ?? this.device?.name ?? this.device?.device_id ?? this.device?.imei ?? this.device?.serial_number;
11
+ }
12
+
13
+ get identifier() {
14
+ return this.device?.imei ?? this.device?.device_id ?? this.device?.internal_id ?? this.device?.serial_number ?? this.device?.public_id;
15
+ }
16
+
17
+ get imageUrl() {
18
+ return this.device?.photo_url;
19
+ }
20
+
21
+ get connectionStatus() {
22
+ return this.device?.connection_status ?? (this.device?.is_online ? 'online' : 'offline');
23
+ }
24
+
25
+ get isOnline() {
26
+ return this.device?.is_online || this.connectionStatus === 'online';
27
+ }
28
+
29
+ @action onClick(event) {
30
+ const { column, onClick } = this.args;
31
+
32
+ if (typeof onClick === 'function') {
33
+ onClick(this.device, event);
34
+ }
35
+
36
+ if (typeof column?.action === 'function') {
37
+ column.action(this.device, event);
38
+ }
39
+
40
+ if (typeof column?.onClick === 'function') {
41
+ column.onClick(this.device, event);
42
+ }
43
+ }
44
+ }