@fleetbase/fleetops-engine
Version:
Fleet & Transport Management Extension for Fleetbase
344 lines (305 loc) • 13.3 kB
JavaScript
import ResourceActionService, { inject as service } from '@fleetbase/ember-core/services/resource-action';
import leafletIcon from '@fleetbase/ember-core/utils/leaflet-icon';
import config from 'ember-get-config';
import { action } from '@ember/object';
import { isArray } from '@ember/array';
import { dasherize } from '@ember/string';
export default class VehicleActionsService extends ResourceActionService {
menuService;
fetch;
maintenanceScheduleActions;
workOrderActions;
maintenanceActions;
get registeredTabs() {
const registeredTabs = this.menuService.getMenuItems('fleet-ops:component:vehicle:details');
return (isArray(registeredTabs) ? registeredTabs : [])
.map((tab) => {
delete tab.route;
if (!tab.key) {
tab.key = tab.id ?? dasherize(tab.label ?? tab.title);
}
return tab;
})
.filter((tab) => !tab.component);
}
get panelTabs() {
return [
{
key: 'overview',
label: 'Overview',
component: 'vehicle/details',
},
{
key: 'positions',
label: 'Positions',
component: 'positions-replay',
},
{
key: 'devices',
label: 'Devices',
component: 'device/manager',
},
{
key: 'schedules',
label: 'Schedules',
component: 'vehicle/details/schedules',
},
{
key: 'work-orders',
label: 'Work Orders',
component: 'vehicle/details/work-orders',
},
{
key: 'maintenance-history',
label: 'Maintenance',
component: 'vehicle/details/maintenance-history',
},
...this.registeredTabs,
];
}
constructor() {
super(...arguments);
this.initialize('vehicle', { status: 'available' });
}
async resolveVehicleResource(vehicle) {
if (typeof vehicle?.then === 'function') {
return await vehicle;
}
if (typeof vehicle?.loadResource === 'function') {
return (await vehicle.loadResource()) ?? vehicle;
}
return vehicle;
}
async reloadIndexResource(vehicle) {
if (vehicle?.meta?._index_resource) {
await vehicle.reload();
}
return vehicle;
}
transition = {
view: (vehicle) => this.transitionTo('management.vehicles.index.details', vehicle),
edit: (vehicle) => this.transitionTo('management.vehicles.index.edit', vehicle),
create: () => this.transitionTo('management.vehicles.index.new'),
};
panel = {
create: (attributes = {}, options = {}) => {
const vehicle = this.createNewInstance(attributes);
return this.resourceContextPanel.open({
content: 'vehicle/form',
title: this.intl.t('common.create-a-new-resource', { resource: this.intl.t('resource.vehicle')?.toLowerCase() }),
saveOptions: {
callback: this.refresh,
},
useDefaultSaveTask: true,
vehicle,
...options,
});
},
edit: async (vehicle, options = {}) => {
vehicle = await this.reloadIndexResource(await this.resolveVehicleResource(vehicle));
return this.resourceContextPanel.open({
content: 'vehicle/form',
title: this.intl.t('common.edit-resource-name', { resourceName: vehicle.name }),
actionButtons: [
{
icon: 'eye',
fn: async () => {
await this.resourceContextPanel.closeAll();
this.panel.view(vehicle);
},
},
],
useDefaultSaveTask: true,
vehicle,
...options,
});
},
view: async (vehicle, options = {}) => {
vehicle = await this.reloadIndexResource(await this.resolveVehicleResource(vehicle));
return this.resourceContextPanel.open({
vehicle,
header: 'vehicle/panel-header',
actionButtons: [
{
icon: 'pencil',
fn: async () => {
await this.resourceContextPanel.closeAll();
this.panel.edit(vehicle);
},
},
],
tabs: this.panelTabs,
...options,
});
},
};
modal = {
create: (attributes = {}, options = {}, saveOptions = {}) => {
const vehicle = this.createNewInstance(attributes);
return this.modalsManager.show('modals/resource', {
resource: vehicle,
title: this.intl.t('common.create-a-new-resource', { resource: this.intl.t('resource.vehicle')?.toLowerCase() }),
acceptButtonText: this.intl.t('common.create-resource', { resource: this.intl.t('resource.vehicle') }),
component: 'vehicle/form',
confirm: (modal) => this.modalTask.perform(modal, 'saveTask', vehicle, { refresh: true, ...saveOptions }),
...options,
});
},
edit: async (vehicle, options = {}, saveOptions = {}) => {
vehicle = await this.reloadIndexResource(await this.resolveVehicleResource(vehicle));
return this.modalsManager.show('modals/resource', {
resource: vehicle,
title: this.intl.t('common.edit-resource-name', { resourceName: vehicle.name }),
acceptButtonText: this.intl.t('common.save-changes'),
saveButtonIcon: 'save',
component: 'vehicle/form',
confirm: (modal) => this.modalTask.perform(modal, 'saveTask', vehicle, { refresh: true, ...saveOptions }),
...options,
});
},
view: async (vehicle, options = {}) => {
vehicle = await this.reloadIndexResource(await this.resolveVehicleResource(vehicle));
return this.modalsManager.show('modals/resource', {
resource: vehicle,
title: vehicle.name,
component: 'vehicle/details',
...options,
});
},
};
scheduleMaintenance(vehicle) {
this.maintenanceScheduleActions.modal.create({ subject: vehicle });
}
createWorkOrder(vehicle) {
this.workOrderActions.modal.create({ target: vehicle });
}
logMaintenance(vehicle) {
this.maintenanceActions.modal.create({ maintainable: vehicle });
}
attachDevice(vehicle, options = {}) {
this.modalsManager.show('modals/attach-device', {
title: this.intl.t('vehicle.prompts.attach-device-title', { vehicleName: vehicle.displayName ?? vehicle.name }),
acceptButtonText: this.intl.t('vehicle.actions.attach-device'),
acceptButtonIcon: 'link',
selectedDevice: null,
vehicle,
confirm: async (modal) => {
const selectedDevice = modal.getOption('selectedDevice');
if (!selectedDevice) {
return this.notifications.warning(this.intl.t('vehicle.prompts.select-device-warning'));
}
modal.startLoading();
try {
await this.fetch.post(`vehicles/${vehicle.id}/attach-device`, { device: selectedDevice.id });
await vehicle.reload?.();
this.notifications.success(this.intl.t('vehicle.prompts.attach-device-success', { vehicleName: vehicle.displayName ?? vehicle.name }));
modal.done();
this.refresh();
} catch (error) {
this.notifications.serverError(error);
modal.stopLoading();
}
},
...options,
});
}
async unassignOrders(vehicle, options = {}) {
let response;
try {
response = await this.fetch.get(`vehicles/${vehicle.id}/assigned-orders`);
} catch (error) {
return this.notifications.serverError(error);
}
const currentOrderId = response.current;
const orders = (response.orders ?? response.data ?? []).map((order) => ({
...order,
is_current_job: [order.id, order.uuid, order.public_id].includes(currentOrderId),
}));
const vehicleName = vehicle.displayName ?? vehicle.display_name ?? vehicle.name;
if (orders.length === 0) {
return this.notifications.warning(this.intl.t('vehicle.prompts.no-assigned-orders-warning', { vehicleName }));
}
this.modalsManager.show('modals/assigned-orders', {
title: this.intl.t('vehicle.prompts.unassign-orders-title', { vehicleName }),
acceptButtonText: this.intl.t('vehicle.actions.unassign-orders'),
acceptButtonIcon: 'truck-ramp-box',
acceptButtonDisabled: true,
subjectLabel: this.intl.t('resource.vehicle'),
subjectName: vehicleName,
orders,
selectedOrderIds: [],
toggleOrder: (order) => {
const orderId = order.id;
const selected = this.modalsManager.getOption('selectedOrderIds') ?? [];
const selectedOrderIds = selected.includes(orderId) ? selected.filter((id) => id !== orderId) : [...selected, orderId];
this.modalsManager.setOption('selectedOrderIds', selectedOrderIds);
this.modalsManager.setOption('acceptButtonDisabled', selectedOrderIds.length === 0);
},
confirm: async (modal) => {
const selectedOrderIds = modal.getOption('selectedOrderIds') ?? [];
const selectedOrders = orders.filter((order) => selectedOrderIds.includes(order.id));
if (selectedOrders.length === 0) {
return this.notifications.warning(this.intl.t('vehicle.prompts.select-orders-warning'));
}
const confirmed = await this.confirmUnassignOrders({
title: this.intl.t('vehicle.prompts.confirm-unassign-orders-title', { count: selectedOrders.length, vehicleName }),
body: this.intl.t('vehicle.prompts.confirm-unassign-orders-body', { vehicleName, orders: this.orderReferenceList(selectedOrders) }),
});
if (!confirmed) {
return;
}
modal.startLoading();
try {
await this.fetch.post(`vehicles/${vehicle.id}/unassign-orders`, { orders: selectedOrderIds });
await vehicle.reload?.();
this.notifications.success(this.intl.t('vehicle.prompts.unassign-orders-success', { count: selectedOrders.length, vehicleName }));
modal.done();
this.refresh();
} catch (error) {
this.notifications.serverError(error);
modal.stopLoading();
}
},
...options,
});
}
locate(vehicle, options = {}) {
const { latitude, longitude, location } = vehicle;
this.modalsManager.show('modals/point-map', {
title: this.intl.t('common.resource-location', { resource: vehicle.displayName }),
acceptButtonText: this.intl.t('common.done'),
hideDeclineButton: true,
resource: vehicle,
popupText: `${vehicle.displayName} (${vehicle.public_id})`,
tooltip: vehicle.positionString,
icon: leafletIcon({
iconUrl: vehicle.avatar ?? config?.defaultValues?.vehicleAvatar,
iconSize: [40, 40],
}),
latitude,
longitude,
location,
...options,
});
}
orderReferenceList(orders) {
const references = orders.map((order) => order.tracking ?? order.public_id ?? order.id).filter(Boolean);
return references.length > 5 ? `${references.slice(0, 5).join(', ')} +${references.length - 5}` : references.join(', ');
}
async confirmUnassignOrders({ title, body }) {
return new Promise((resolve) => {
this.modalsManager.confirm({
title,
body,
confirm: (_modal, done) => {
done();
resolve(true);
},
decline: (_modal, done) => {
done();
resolve(false);
},
});
});
}
}