@fleetbase/fleetops-engine 0.6.54 → 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.
- package/addon/components/admin/routing-settings.hbs +2 -1
- package/addon/components/cell/attached-vehicle.hbs +10 -0
- package/addon/components/cell/attached-vehicle.js +58 -0
- package/addon/components/cell/device-identity.hbs +24 -0
- package/addon/components/cell/device-identity.js +129 -0
- package/addon/components/cell/driver-identity.hbs +33 -0
- package/addon/components/cell/driver-identity.js +143 -0
- package/addon/components/cell/equipment-identity.hbs +5 -0
- package/addon/components/cell/equipment-identity.js +48 -0
- package/addon/components/cell/part-identity.hbs +5 -0
- package/addon/components/cell/part-identity.js +63 -0
- package/addon/components/cell/telematic-device.hbs +38 -0
- package/addon/components/cell/telematic-device.js +44 -0
- package/addon/components/cell/telematic-provider.hbs +33 -16
- package/addon/components/cell/telematic-provider.js +50 -8
- package/addon/components/cell/vehicle-identity.hbs +33 -0
- package/addon/components/cell/vehicle-identity.js +138 -0
- package/addon/components/device/form.hbs +2 -2
- package/addon/components/device/form.js +7 -0
- package/addon/components/device/panel-header.hbs +45 -20
- package/addon/components/device/panel-header.js +49 -1
- package/addon/components/device/panel-tabs/events.hbs +30 -0
- package/addon/components/device/panel-tabs/events.js +128 -0
- package/addon/components/device/panel-tabs/sensors.hbs +30 -0
- package/addon/components/device/panel-tabs/sensors.js +93 -0
- package/addon/components/device/panel-tabs/vehicle.hbs +68 -0
- package/addon/components/device/panel-tabs/vehicle.js +90 -0
- package/addon/components/equipment/form.js +8 -1
- package/addon/components/layout/fleet-ops-sidebar.hbs +1 -1
- package/addon/components/layout/fleet-ops-sidebar.js +8 -0
- package/addon/components/map/drawer/device-event-listing.js +2 -0
- package/addon/components/map/drawer/driver-listing.js +2 -0
- package/addon/components/map/drawer/place-listing.js +2 -0
- package/addon/components/map/drawer/position-listing.hbs +11 -6
- package/addon/components/map/drawer/position-listing.js +17 -4
- package/addon/components/map/drawer/vehicle-listing.js +2 -0
- package/addon/components/sensor/panel-header.hbs +47 -15
- package/addon/components/sensor/panel-header.js +59 -1
- package/addon/components/table/cell/dropdown/action-item.hbs +31 -0
- package/addon/components/table/cell/dropdown/action-item.js +62 -0
- package/addon/components/table/cell/dropdown.hbs +28 -0
- package/addon/components/table/cell/dropdown.js +91 -0
- package/addon/components/vendor/panel-header.hbs +82 -0
- package/addon/components/vendor/panel-header.js +48 -0
- package/addon/controllers/connectivity/devices/index/details/vehicle.js +49 -1
- package/addon/controllers/connectivity/devices/index.js +144 -129
- package/addon/controllers/connectivity/events/details.js +3 -0
- package/addon/controllers/connectivity/events/index.js +187 -11
- package/addon/controllers/connectivity/sensors/index.js +156 -14
- package/addon/controllers/connectivity/telematics/details/devices.js +126 -134
- package/addon/controllers/maintenance/equipment/index.js +1 -2
- package/addon/controllers/maintenance/parts/index.js +1 -2
- package/addon/controllers/management/drivers/index.js +82 -42
- package/addon/controllers/management/fuel-reports/index.js +63 -4
- package/addon/controllers/management/issues/index.js +63 -4
- package/addon/controllers/management/vehicles/index.js +33 -6
- package/addon/routes/connectivity/devices/index/details/vehicle.js +24 -2
- package/addon/routes/connectivity/devices/index.js +7 -0
- package/addon/routes/connectivity/events/details.js +3 -0
- package/addon/routes/connectivity/events/index.js +6 -2
- package/addon/routes/connectivity/sensors/index.js +7 -2
- package/addon/routes/connectivity/telematics/details/devices.js +2 -0
- package/addon/services/device-actions.js +30 -0
- package/addon/services/device-event-actions.js +1 -1
- package/addon/services/equipment-actions.js +1 -0
- package/addon/services/sensor-actions.js +3 -0
- package/addon/services/service-rate-actions.js +39 -1
- package/addon/services/vehicle-actions.js +24 -12
- package/addon/services/vendor-actions.js +3 -0
- package/addon/styles/fleetops-engine.css +8 -0
- package/addon/templates/connectivity/devices/index/details/vehicle.hbs +64 -1
- package/addon/templates/management/vendors/index/details.hbs +2 -2
- package/addon/templates/settings/notifications.hbs +2 -1
- package/addon/templates/settings/routing.hbs +1 -0
- package/addon/utils/device-table-columns.js +288 -0
- package/addon/utils/identity-cell-resource.js +20 -0
- package/addon/utils/map-drawer-dropdown-position.js +47 -0
- package/addon/utils/trackable-option.js +93 -0
- package/app/components/cell/attached-vehicle.js +1 -0
- package/app/components/cell/device-identity.js +1 -0
- package/app/components/cell/driver-identity.js +1 -0
- package/app/components/cell/equipment-identity.js +1 -0
- package/app/components/cell/part-identity.js +1 -0
- package/app/components/cell/telematic-device.js +1 -0
- package/app/components/cell/vehicle-identity.js +1 -0
- package/app/components/device/panel-tabs/events.hbs +30 -0
- package/app/components/device/panel-tabs/events.js +128 -0
- package/app/components/device/panel-tabs/sensors.hbs +30 -0
- package/app/components/device/panel-tabs/sensors.js +93 -0
- package/app/components/device/panel-tabs/vehicle.hbs +68 -0
- package/app/components/device/panel-tabs/vehicle.js +90 -0
- package/app/components/table/cell/dropdown/action-item.js +1 -0
- package/app/components/table/cell/dropdown.js +1 -0
- package/app/components/vendor/panel-header.js +1 -0
- package/app/controllers/connectivity/events/{index/details.js → details.js} +1 -1
- package/app/routes/connectivity/events/{index/details.js → details.js} +1 -1
- package/app/templates/connectivity/events/{index/details.js → details.js} +1 -1
- package/app/utils/map-drawer-dropdown-position.js +1 -0
- package/app/utils/trackable-option.js +1 -0
- package/composer.json +1 -1
- package/extension.json +1 -1
- package/package.json +4 -4
- package/server/src/Console/Commands/SyncTelematics.php +4 -4
- package/server/src/Http/Controllers/Internal/v1/DeviceController.php +18 -1
- package/server/src/Http/Controllers/Internal/v1/DeviceEventController.php +1 -1
- package/server/src/Http/Controllers/Internal/v1/LiveController.php +1 -1
- package/server/src/Http/Controllers/Internal/v1/ServiceRateController.php +82 -13
- package/server/src/Http/Filter/DeviceEventFilter.php +21 -0
- package/server/src/Http/Filter/DeviceFilter.php +24 -1
- package/server/src/Http/Resources/v1/Index/Vehicle.php +19 -0
- package/server/src/Models/Device.php +2 -0
- package/server/src/Models/DeviceEvent.php +81 -0
- package/server/src/Models/Equipment.php +55 -0
- package/server/src/Support/Telematics/Providers/AfaqyProvider.php +1 -0
- package/server/src/Support/Telematics/Providers/FlespiProvider.php +89 -15
- package/server/src/Support/Telematics/Providers/GeotabProvider.php +123 -35
- package/server/src/Support/Telematics/Providers/SafeeProvider.php +26 -2
- package/server/src/Support/Telematics/Providers/SamsaraProvider.php +122 -14
- package/server/src/routes.php +0 -1
- package/server/tests/DeviceFilterTest.php +9 -0
- package/server/tests/DriverVehicleStatusDefaultTest.php +19 -2
- package/server/tests/EquipmentAssignmentMorphTypeTest.php +35 -0
- package/server/tests/ServiceRateRouteValidationTest.php +101 -0
- package/server/tests/TelematicsHardeningTest.php +238 -7
- package/addon/controllers/connectivity/events/index/details.js +0 -3
- package/addon/routes/connectivity/events/index/details.js +0 -3
- /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
|
+
}
|