UNPKG

@fleetbase/fleetops-engine

Version:

Fleet & Transport Management Extension for Fleetbase

414 lines (338 loc) 14 kB
import Controller from '@ember/controller'; import { action } from '@ember/object'; import { inject as service } from '@ember/service'; import { tracked } from '@glimmer/tracking'; export default class ConnectivityTelematicsDetailsAttachmentsController extends Controller { @service deviceActions; @service fetch; @service hostRouter; @service intl; @service modalsManager; @service notifications; @tracked queryParams = ['query', 'status', 'vehicle', 'sort']; @tracked telematic; @tracked query; @tracked status; @tracked vehicle; @tracked sort = '-updated_at'; @tracked selectedDevice = null; @tracked selectedVehicle = null; @tracked isRefreshing = false; get syncedDevices() { return Array.from(this.model?.devices ?? this.model ?? []); } get loadError() { return this.model?.error; } get totalSyncedDevices() { return this.model?.meta?.total ?? this.syncedDevices.length; } get devices() { return [...this.unattachedDevices, ...this.attachedDevices]; } get unattachedDevices() { return this.syncedDevices.filter((device) => !device.attachable_uuid && this.deviceMatchesQuery(device) && this.deviceMatchesStatus(device)); } get attachedDevices() { return this.syncedDevices.filter((device) => device.attachable_uuid && this.deviceMatchesQuery(device) && this.deviceMatchesStatus(device) && this.deviceMatchesVehicle(device)); } get onlineDevicesCount() { return this.syncedDevices.filter((device) => device.is_online || device.connection_status === 'online' || device.status === 'online' || device.status === 'active').length; } get attachedDevicesCount() { return this.syncedDevices.filter((device) => device.attachable_uuid).length; } get unattachedDevicesCount() { return this.syncedDevices.filter((device) => !device.attachable_uuid).length; } get mappedVehiclesCount() { const vehicleIds = new Set(this.syncedDevices.filter((device) => device.attachable_uuid).map((device) => device.attachable_uuid)); return vehicleIds.size; } get vehicleGroups() { const groups = new Map(); for (const device of this.attachedDevices) { this.addDeviceToVehicleGroup(groups, device); } return this.sortVehicleGroups(groups); } addDeviceToVehicleGroup(groups, device) { const key = device.attachable_uuid; if (!groups.has(key)) { const vehicle = device.attachable; groups.set(key, { id: key, name: this.getDeviceVehicleName(device), vehicle, devices: [], }); } groups.get(key).devices.push(device); } sortVehicleGroups(groups) { return Array.from(groups.values()).sort((a, b) => String(a.name).localeCompare(String(b.name))); } get normalizedQuery() { return String(this.query ?? '') .trim() .toLowerCase(); } deviceMatchesQuery(device) { const query = this.normalizedQuery; if (!query) { return true; } return [ device.displayName, device.name, device.device_id, device.serial_number, device.imei, device.public_id, device.attached_to_name, device.attachable?.displayName, device.attachable?.display_name, device.attachable?.name, device.status, device.connection_status, ] .filter(Boolean) .some((value) => String(value).toLowerCase().includes(query)); } deviceMatchesStatus(device) { return !this.status || device.status === this.status || device.connection_status === this.status; } deviceMatchesVehicle(device) { return !this.vehicle || device.attachable_uuid === this.vehicle; } get hasActiveFilters() { return Boolean(this.query || this.status || this.vehicle); } get hasSyncedDevices() { return this.syncedDevices.length > 0; } get hasVisibleDevices() { return this.devices.length > 0; } get hasOnlyUnattachedDevices() { return this.unattachedDevices.length > 0 && this.vehicleGroups.length === 0 && !this.hasActiveFilters; } get allMapped() { return this.hasSyncedDevices && this.unattachedDevices.length === 0 && this.vehicleGroups.length > 0 && !this.hasActiveFilters; } get emptyStateVariant() { if (this.loadError) { return 'error'; } if (!this.hasSyncedDevices) { return 'not_synced'; } if (this.hasActiveFilters && !this.hasVisibleDevices) { return 'filtered_empty'; } if (this.allMapped) { return 'empty'; } if (this.hasOnlyUnattachedDevices) { return 'unattached_only'; } return null; } get emptyStateContent() { switch (this.emptyStateVariant) { case 'error': return { tone: 'danger', icon: 'triangle-exclamation', title: 'Unable to load device attachments', message: 'Fleetbase could not load the full provider device list. Refresh the tab or review the connection logs.', primaryText: 'Refresh', primaryIcon: 'refresh', primaryAction: this.refresh, secondaryText: 'Go to Logs', secondaryIcon: 'history', secondaryAction: this.goToLogs, }; case 'not_synced': return { tone: 'warning', icon: 'satellite-dish', title: 'Sync devices before mapping vehicles', message: 'Vehicle attachment starts after provider devices are synced into this connection.', primaryText: 'Go to Devices', primaryIcon: 'microchip', primaryAction: this.goToDevices, }; case 'filtered_empty': return { tone: 'info', icon: 'filter', title: 'No mappings match these filters', message: 'Clear the search and filters to return to all synced devices and vehicle mappings.', primaryText: 'Clear filters', primaryIcon: 'filter', primaryAction: this.clearFilters, }; case 'empty': return { tone: 'success', icon: 'check', title: 'All synced devices are mapped', message: 'Every synced provider device in this connection is currently attached to a vehicle.', }; case 'unattached_only': return { tone: 'warning', icon: 'link-slash', title: 'Attach synced devices to vehicles', message: 'Use the unattached devices section below to map each device deliberately. Bulk auto-attachment is intentionally not available here.', }; default: return null; } } get metrics() { return [ { label: 'Synced devices', value: this.totalSyncedDevices, icon: 'microchip', accentClass: 'fleetops-connectivity-kpi-accent-blue' }, { label: 'Vehicles mapped', value: this.mappedVehiclesCount, icon: 'truck', accentClass: 'fleetops-connectivity-kpi-accent-blue' }, { label: 'Attached devices', value: this.attachedDevicesCount, icon: 'link', accentClass: 'fleetops-connectivity-kpi-accent-green' }, { label: 'Unattached devices', value: this.unattachedDevicesCount, icon: 'link-slash', accentClass: 'fleetops-connectivity-kpi-accent-amber' }, { label: 'Online devices', value: this.onlineDevicesCount, icon: 'signal', accentClass: 'fleetops-connectivity-kpi-accent-green' }, ]; } get visibleDevicesCount() { return this.devices.length; } get filteredUnattachedCount() { return this.unattachedDevices.length; } get filteredAttachedCount() { return this.attachedDevices.length; } get selectedDeviceName() { return this.selectedDevice?.displayName ?? this.selectedDevice?.name ?? this.selectedDevice?.device_id; } @action updateQuery(event) { this.query = event.target.value; } @action updateStatus(event) { this.status = event.target.value || null; } @action updateVehicle(valueOrEvent) { const value = valueOrEvent?.target ? valueOrEvent.target.value : valueOrEvent; this.vehicle = value || null; } @action updateSelectedVehicle(vehicle) { this.selectedVehicle = vehicle; this.vehicle = vehicle?.id ?? null; } @action clearFilters() { this.query = null; this.status = null; this.vehicle = null; this.selectedVehicle = null; } @action goToDevices() { return this.hostRouter.transitionTo('console.fleet-ops.connectivity.telematics.details.devices', this.telematic); } @action goToLogs() { return this.hostRouter.transitionTo('console.fleet-ops.connectivity.telematics.details.logs', this.telematic); } @action async refresh() { this.isRefreshing = true; try { return await this.hostRouter.refresh(); } finally { this.isRefreshing = false; } } @action selectUnattachedDevice(device) { if (this.selectedDevice === device) { this.selectedDevice = null; return; } this.selectedDevice = device; } @action clearSelectedDevice() { this.selectedDevice = null; } @action attachSelectedDeviceToGroup(group) { if (!this.selectedDevice) { return; } return this.attachDeviceToVehicle(this.selectedDevice, group.vehicle ?? { id: group.id, name: group.name }); } @action openAttachDeviceModal(device) { this.modalsManager.show('modals/attach-telematic-device', { title: this.intl.t('device.prompts.attach-device-to-vehicle-title', { deviceName: device.displayName ?? device.name ?? device.device_id ?? this.intl.t('resource.device') }), acceptButtonText: this.intl.t('device.actions.attach-to-vehicle'), device, selectedVehicle: null, confirm: async (modal) => { const selectedVehicle = modal.getOption('selectedVehicle'); if (!selectedVehicle) { return; } modal.startLoading(); try { await this.attachDeviceToVehicle(device, selectedVehicle); modal.done(); } catch (error) { this.notifications.serverError(error); modal.stopLoading(); } }, }); } async attachDeviceToVehicle(device, selectedVehicle) { const response = await this.fetch.post(`devices/${device.id}/attach`, { vehicle: selectedVehicle.id }); this.applyDeviceAttachment(device, selectedVehicle, response?.device); this.selectedDevice = null; this.notifications.success(this.intl.t('device.prompts.attach-to-vehicle-success')); } @action detachDevice(device) { const deviceName = device.displayName ?? device.name ?? device.device_id ?? this.intl.t('resource.device'); this.modalsManager.confirm({ title: this.intl.t('device.prompts.detach-telematic-device-title', { deviceName }), body: this.intl.t('device.prompts.detach-telematic-device-body', { deviceName }), confirm: async (modal) => { modal.startLoading(); try { const response = await this.fetch.post(`devices/${device.id}/detach`); this.applyDeviceDetachment(device, response?.device); this.notifications.success(this.intl.t('device.prompts.detach-from-vehicle-success')); modal.done(); } catch (error) { this.notifications.serverError(error); modal.stopLoading(); } }, }); } getDeviceVehicleName(device) { return device.attached_to_name ?? device.attachable?.display_name ?? device.attachable?.name ?? 'Unknown vehicle'; } applyDeviceAttachment(device, selectedVehicle, serverDevice = {}) { this.updateDevice(device, { attachable_uuid: serverDevice.attachable_uuid ?? selectedVehicle.id, attachable_type: serverDevice.attachable_type ?? 'fleet-ops:vehicle', attached_to_name: serverDevice.attached_to_name ?? selectedVehicle.displayName ?? selectedVehicle.display_name ?? selectedVehicle.name, attachable: serverDevice.attachable ?? selectedVehicle, }); } applyDeviceDetachment(device, serverDevice = {}) { this.updateDevice(device, { attachable_uuid: serverDevice.attachable_uuid ?? null, attachable_type: serverDevice.attachable_type ?? null, attached_to_name: serverDevice.attached_to_name ?? null, attachable: serverDevice.attachable ?? null, }); } updateDevice(device, properties) { if (typeof device.setProperties === 'function') { device.setProperties(properties); return; } Object.assign(device, properties); } }