@fleetbase/fleetops-engine
Version:
Fleet & Transport Management Extension for Fleetbase
424 lines (380 loc) • 19.9 kB
JavaScript
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { inject as service } from '@ember/service';
import { action } from '@ember/object';
import isMenuItemActive from '@fleetbase/ember-ui/utils/is-menu-item-active';
const SECTION_REGISTRY_KEYS = {
operations: 'universeOperationsMenuItems',
management: 'universeManagementMenuItems',
maintenance: 'universeMaintenanceMenuItems',
connectivity: 'universeConnectivityMenuItems',
analytics: 'universeAnalyticsMenuItems',
settings: 'universeSettingsMenuItems',
};
export default class LayoutFleetOpsSidebarComponent extends Component {
universe;
menuService;
intl;
abilities;
fetch;
routePrefix = 'console.fleet-ops.';
universeMenuItems = [];
universeOperationsMenuItems = [];
universeManagementMenuItems = [];
universeConnectivityMenuItems = [];
universeMaintenanceMenuItems = [];
universeAnalyticsMenuItems = [];
universeSettingsMenuItems = [];
universeMenuPanels = [];
constructor() {
super(...arguments);
this.createMenuItemsFromUniverseRegistry();
}
get createOrderAction() {
return {
label: this.intl.t('common.create-new-resource', { resource: this.intl.t('resource.order')?.toLowerCase() }),
icon: 'paper-plane',
iconPrefix: 'fas',
buttonClass: 'fleet-ops-sidebar-primary-action',
permission: 'fleet-ops create order',
onClick: this.onClickCreateOrder,
};
}
get navigationItems() {
const coreBranches = [
this.createBranch({
id: 'operations',
label: this.intl.t('menu.operations'),
icon: 'circle-nodes',
route: 'operations',
defaultRoute: 'operations.orders',
keywords: ['dispatch', 'orders', 'live map', 'track vehicles', 'drivers online', 'vehicles online'],
children: this.operationsItems,
}),
this.createBranch({
id: 'resources',
label: this.intl.t('menu.resources'),
icon: 'truck',
route: 'management',
defaultRoute: 'management.index',
requiresVisibleChildren: true,
keywords: ['drivers', 'vehicles', 'fleets', 'contacts', 'places'],
children: this.resourcesItems,
}),
this.createBranch({
id: 'maintenance',
label: this.intl.t('menu.maintenance'),
icon: 'cog',
route: 'maintenance',
defaultRoute: 'maintenance.index',
requiresVisibleChildren: true,
keywords: ['work orders', 'equipment', 'parts'],
children: this.maintenanceItems,
}),
this.createBranch({
id: 'connectivity',
label: this.intl.t('menu.connectivity'),
icon: 'satellite-dish',
route: 'connectivity',
defaultRoute: 'connectivity.telematics',
requiresVisibleChildren: true,
keywords: ['telematics', 'fuel integrations', 'devices', 'sensors'],
children: this.connectivityItems,
}),
this.createBranch({
id: 'analytics',
label: this.intl.t('menu.analytics'),
icon: 'chart-line',
route: 'analytics',
defaultRoute: 'analytics.index',
requiresVisibleChildren: true,
keywords: ['reports', 'metrics'],
children: this.analyticsItems,
}),
this.createBranch({
id: 'settings',
label: this.intl.t('menu.settings'),
icon: 'gear',
route: 'settings',
defaultRoute: 'settings.index',
requiresVisibleChildren: true,
keywords: ['configuration', 'navigator', 'map', 'routing', 'notifications'],
children: this.settingsItems,
}),
];
return [...coreBranches, ...this.registryRootItems, ...this.registryPanelItems].filter((item) => item.children?.length || item.route || item.url || item.onClick);
}
get operationsItems() {
return this.withRegistryItems('operations', [
this.createItem('menu.orders', 'map-location-dot', 'operations.orders', 'fleet-ops list order', 'fleet-ops see order', [
'dashboard',
'orders',
'dispatch',
'tracking',
'create order',
'live map',
]),
this.createItem('menu.orchestrator', 'circle-nodes', 'operations.orchestrator', 'fleet-ops list order', 'fleet-ops see order', ['optimize', 'allocate', 'dispatch']),
this.createItem('menu.scheduler', 'calendar-day', 'operations.scheduler', 'fleet-ops list order', 'fleet-ops see order', ['schedule', 'calendar']),
this.createItem('menu.order-config', 'diagram-project', 'operations.order-config', 'fleet-ops list order-config', 'fleet-ops see order-config', [
'configuration',
'fields',
'entities',
]),
this.createItem('menu.service-rates', 'file-invoice-dollar', 'operations.service-rates', 'fleet-ops list service-rate', 'fleet-ops see service-rate', ['rates', 'pricing']),
]);
}
get resourcesItems() {
return this.withRegistryItems('management', [
this.createHubItem('Resources Hub', 'layer-group', 'management.index', 'fleet-ops list driver', 'fleet-ops see driver', [
'resources hub',
'resource dashboard',
'resource readiness',
]),
this.createItem('menu.drivers', 'id-card', 'management.drivers', 'fleet-ops list driver', 'fleet-ops see driver', ['driver', 'online drivers']),
this.createItem('menu.vehicles', 'truck', 'management.vehicles', 'fleet-ops list vehicle', 'fleet-ops see vehicle', ['vehicle', 'track vehicles', 'online vehicles']),
this.createItem('menu.fleets', 'user-group', 'management.fleets', 'fleet-ops list fleet', 'fleet-ops see fleet', ['fleet', 'teams']),
this.createItem('menu.vendors', 'warehouse', 'management.vendors', 'fleet-ops list vendor', 'fleet-ops see vendor'),
this.createItem('menu.contacts', 'address-book', 'management.contacts', 'fleet-ops list contact', 'fleet-ops see contact'),
this.createItem('menu.places', 'location-dot', 'management.places', 'fleet-ops list place', 'fleet-ops see place'),
this.createItem('menu.fuel-reports', 'gas-pump', 'management.fuel-reports', 'fleet-ops list fuel-report', 'fleet-ops see fuel-report'),
this.createItem('menu.fuel-transactions', 'credit-card', 'management.fuel-transactions', 'fleet-ops list fuel-report', 'fleet-ops see fuel-report'),
this.createItem('menu.issues', 'triangle-exclamation', 'management.issues', 'fleet-ops list issue', 'fleet-ops see issue'),
]);
}
get maintenanceItems() {
return this.withRegistryItems('maintenance', [
this.createHubItem('Maintenance Hub', 'wrench', 'maintenance.index', 'fleet-ops list maintenance-schedule', 'fleet-ops see maintenance-schedule', [
'maintenance hub',
'service readiness',
'maintenance control panel',
]),
this.createItem('menu.schedules', 'calendar-alt', 'maintenance.schedules', 'fleet-ops list maintenance-schedule', 'fleet-ops see maintenance-schedule'),
this.createItem('menu.work-orders', 'clipboard-list', 'maintenance.work-orders', 'fleet-ops list work-order', 'fleet-ops see work-order'),
this.createItem('menu.maintenances', 'history', 'maintenance.maintenances', 'fleet-ops list maintenance', 'fleet-ops see maintenance'),
this.createItem('menu.equipment', 'trailer', 'maintenance.equipment', 'fleet-ops list equipment', 'fleet-ops see equipment'),
this.createItem('menu.parts', 'cog', 'maintenance.parts', 'fleet-ops list part', 'fleet-ops see part'),
]);
}
get connectivityItems() {
return this.withRegistryItems('connectivity', [
this.createHubItem(this.intl.t('menu.telematics'), 'satellite-dish', 'connectivity.telematics', 'fleet-ops list telematic', 'fleet-ops see telematic', ['connectivity hub']),
this.createItem('menu.fuel-providers', 'gas-pump', 'connectivity.fuel-providers', 'fleet-ops list fuel-report', 'fleet-ops see fuel-report', ['fuel integrations']),
this.createItem('menu.devices', 'hard-drive', 'connectivity.devices', 'fleet-ops list device', 'fleet-ops see device'),
this.createItem('menu.sensors', 'temperature-full', 'connectivity.sensors', 'fleet-ops list sensor', 'fleet-ops see sensor'),
this.createItem('menu.events', 'stream', 'connectivity.events', 'fleet-ops list device-event', 'fleet-ops see device-event'),
]);
}
get analyticsItems() {
return this.withRegistryItems('analytics', [
this.createHubItem('Dashboard', 'chart-line', 'analytics.index', 'iam list report', 'fleet-ops see report', ['dashboard', 'fleetops dashboard', 'metrics']),
this.createItem('menu.reports', 'file-import', 'analytics.reports', 'iam list report', 'fleet-ops see report'),
]);
}
get settingsItems() {
return this.withRegistryItems('settings', [
this.createHubItem('Settings Hub', 'sliders', 'settings.index', 'fleet-ops view navigator-settings', 'fleet-ops see navigator-settings', [
'settings hub',
'configuration dashboard',
'setup focus',
]),
this.createItem('menu.navigator-app', 'location-arrow', 'settings.navigator-app', 'fleet-ops view navigator-settings', 'fleet-ops see navigator-settings'),
this.createItem('menu.map', 'map', 'settings.map', 'fleet-ops view map-settings', 'fleet-ops see map-settings'),
this.createItem('menu.payments', 'cash-register', 'settings.payments', 'fleet-ops view payments', 'fleet-ops see payments'),
this.createItem('menu.notifications', 'bell', 'settings.notifications', 'fleet-ops view notification-settings', 'fleet-ops see notification-settings'),
this.createItem('menu.routing', 'route', 'settings.routing', 'fleet-ops view routing-settings', 'fleet-ops see routing-settings'),
this.createItem('menu.orchestrator', 'circle-nodes', 'settings.orchestrator', 'fleet-ops view routing-settings', 'fleet-ops see routing-settings'),
this.createItem('menu.scheduling', 'calendar-days', 'settings.scheduling', 'fleet-ops view scheduling-settings', 'fleet-ops see scheduling-settings'),
this.createItem('menu.custom-fields', 'pen-to-square', 'settings.custom-fields', 'fleet-ops view custom-field', 'fleet-ops see custom-field'),
this.createItem('menu.avatars', 'icons', 'settings.avatars', 'fleet-ops view avatar', 'fleet-ops see avatar'),
]);
}
get registryRootItems() {
return this.sortByPriority(this.universeMenuItems.filter((item) => !item.renderComponentInPlace).map((item) => this.registryItem(item)));
}
get registryPanelItems() {
return this.sortByPriority(
this.universeMenuPanels.map((panel) => {
const children = this.sortByPriority((panel.items ?? []).filter((item) => !item.renderComponentInPlace).map((item) => this.registryItem(item)));
return {
id: panel.id ?? panel.slug ?? panel.title,
label: panel.intl ? this.intl.t(panel.intl) : panel.title,
icon: panel.icon,
visible: panel.visible,
permission: panel.permission,
priority: panel.priority,
keywords: [panel.slug, panel.title, panel.intl].filter(Boolean),
children,
};
})
);
}
get footerRegistryComponents() {
const sectionItems = Object.entries(SECTION_REGISTRY_KEYS).reduce((components, [section, propertyName]) => {
components[section] = this[propertyName].filter((item) => item.renderComponentInPlace);
return components;
}, {});
return {
root: this.universeMenuItems.filter((item) => item.renderComponentInPlace),
resources: sectionItems.management,
...sectionItems,
};
}
createMenuItemsFromUniverseRegistry() {
const registeredMenuItems = this.menuService.getMenuItems('engine:fleet-ops');
this.universeMenuPanels = this.menuService.getMenuPanels('engine:fleet-ops');
this.universeMenuItems = registeredMenuItems.filter((menuItem) => menuItem.section === undefined);
this.universeOperationsMenuItems = registeredMenuItems.filter((menuItem) => menuItem.section === 'operations');
this.universeManagementMenuItems = registeredMenuItems.filter((menuItem) => menuItem.section === 'management');
this.universeMaintenanceMenuItems = registeredMenuItems.filter((menuItem) => menuItem.section === 'maintenance');
this.universeConnectivityMenuItems = registeredMenuItems.filter((menuItem) => menuItem.section === 'connectivity');
this.universeAnalyticsMenuItems = registeredMenuItems.filter((menuItem) => menuItem.section === 'analytics');
this.universeSettingsMenuItems = registeredMenuItems.filter((menuItem) => menuItem.section === 'settings');
}
createBranch({ id, label, icon, route, defaultRoute, requiresVisibleChildren = false, children, keywords = [] }) {
return {
id,
label,
icon,
route: this.fullRoute(route),
defaultRoute: this.fullRoute(defaultRoute),
requiresVisibleChildren,
children: children.filter((item) => item.visible !== false),
keywords,
};
}
createItem(intl, icon, route, permission, ability, keywords = []) {
return {
priority: this.defaultPriorityForRoute(route),
label: this.intl.t(intl),
description: this.intl.t(intl),
icon,
route: this.fullRoute(route),
permission,
visiblePermission: ability,
keywords: [intl, route, ...keywords].filter(Boolean),
};
}
createHubItem(label, icon, route, _permission, _ability, keywords = []) {
return {
pinnedFirst: true,
priority: this.defaultPriorityForRoute(route),
label,
description: label,
icon,
route: this.fullRoute(route),
isNavigationHub: true,
keywords: [label, route, ...keywords].filter(Boolean),
};
}
registryItem(menuItem) {
const registryMenuItem = {
...menuItem,
_virtual: true,
label: menuItem.intl ? this.intl.t(menuItem.intl) : (menuItem.title ?? menuItem.label),
description: menuItem.description,
icon: menuItem.icon,
iconPrefix: menuItem.iconPrefix,
permission: menuItem.permission,
visible: menuItem.visible,
keywords: [menuItem.slug, menuItem.view, menuItem.section, menuItem.title, menuItem.label, menuItem.intl, ...(menuItem.keywords ?? [])].filter(Boolean),
activeWhen: () => isMenuItemActive(menuItem.section, menuItem.slug, menuItem.view),
};
registryMenuItem.onClick = () => this.universe.transitionMenuItem(`${this.routePrefix}virtual`, registryMenuItem);
return registryMenuItem;
}
withRegistryItems(section, items) {
const registryProperty = SECTION_REGISTRY_KEYS[section];
const registryItems = (this[registryProperty] ?? []).filter((item) => !item.renderComponentInPlace).map((item) => this.registryItem(item));
return this.sortByPriority([...items, ...registryItems]);
}
sortByPriority(items = []) {
return [...items]
.map((item, index) => ({ item, index }))
.sort((a, b) => {
if (a.item.pinnedFirst !== b.item.pinnedFirst) {
return a.item.pinnedFirst ? -1 : 1;
}
const priorityOrder = (a.item.priority ?? 0) - (b.item.priority ?? 0);
if (priorityOrder !== 0) {
return priorityOrder;
}
return a.index - b.index;
})
.map(({ item }) => item);
}
defaultPriorityForRoute(route) {
const priorities = {
'operations.orders': 0,
'operations.orchestrator': 1,
'operations.scheduler': 2,
'operations.order-config': 3,
'operations.service-rates': 4,
'management.index': 0,
'management.drivers': 1,
'management.vehicles': 2,
'management.fleets': 3,
'management.vendors': 4,
'management.contacts': 5,
'management.places': 6,
'management.fuel-reports': 7,
'management.fuel-transactions': 8,
'management.issues': 9,
'maintenance.index': 0,
'maintenance.schedules': 1,
'maintenance.work-orders': 2,
'maintenance.maintenances': 3,
'maintenance.equipment': 4,
'maintenance.parts': 5,
'connectivity.telematics': 0,
'connectivity.fuel-providers': 1,
'connectivity.devices': 2,
'connectivity.sensors': 3,
'connectivity.events': 4,
'analytics.index': 0,
'analytics.reports': 1,
'settings.index': 0,
'settings.navigator-app': 1,
'settings.map': 2,
'settings.payments': 3,
'settings.notifications': 4,
'settings.routing': 5,
'settings.orchestrator': 6,
'settings.scheduling': 7,
'settings.custom-fields': 8,
'settings.avatars': 9,
};
return priorities[route] ?? 0;
}
fullRoute(route) {
if (!route || route.startsWith('console.')) {
return route;
}
return `${this.routePrefix}${route}`;
}
onClickCreateOrder() {
if (typeof this.args.onClickCreateOrder === 'function') {
this.args.onClickCreateOrder();
}
}
shouldSyncInitialActiveParent({ activePath = [], currentURL }) {
const [parent, child] = activePath;
const normalizedURL = (currentURL ?? '').split('?')[0].replace(/\/+$/, '') || '/';
const isFleetOpsRootURL = normalizedURL === '/fleet-ops';
const isDefaultOrdersLanding = parent?.id === 'operations' && child?.route === this.fullRoute('operations.orders') && isFleetOpsRootURL;
return !isDefaultOrdersLanding;
}
async searchNavigation({ query, limit = 12 }) {
const trimmedQuery = query?.trim();
if (!trimmedQuery) {
return [];
}
try {
const response = await this.fetch.get('search', { query: trimmedQuery, limit }, { namespace: 'int/v1' });
return response.results ?? [];
} catch (_) {
return [];
}
}
}