Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions addon/components/admin/routing-settings.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,80 @@
Configure system-wide routing engine defaults here. Organizations can optionally override these values from their own routing settings.
</InfoBlock>

<ContentPanel @title="Tracking Defaults" @open={{true}} @wrapperClass="bordered-classic">
<InputGroup @name="Default Tracking Provider" @helpText="Default provider for organization-level tracking intelligence unless an organization overrides it.">
<Select
class="w-full"
@value={{this.trackingProvider}}
@options={{this.trackingProviderOptions}}
@optionLabel="label"
@optionValue="value"
@placeholder="Select default tracking provider"
@onSelect={{fn (mut this.trackingProvider)}}
/>
</InputGroup>
<InputGroup @name="Default Fallback Providers" @helpText="Providers used when the selected tracking provider is unavailable.">
<div class="fleetbase-model-select fleetbase-power-select ember-model-select">
<PowerSelectMultiple
@searchEnabled={{true}}
@options={{this.trackingProviderOptions}}
@selected={{this.selectedTrackingFallbackOptions}}
@onChange={{this.setTrackingFallbacks}}
@placeholder="Select default fallback providers"
@triggerClass="form-select form-input form-input-sm flex-1"
as |provider|
>
{{provider.label}}
</PowerSelectMultiple>
</div>
</InputGroup>
<div class="flex justify-end">
<Button
@type="default"
@size="xs"
@icon={{if this.showTrackingAdvancedSettings "chevron-up" "chevron-down"}}
@text="Advanced Settings"
@onClick={{this.toggleTrackingAdvancedSettings}}
/>
</div>
{{#if this.showTrackingAdvancedSettings}}
<div class="mt-4 space-y-4 rounded-md border border-gray-200 bg-gray-50 p-3 dark:border-gray-700 dark:bg-gray-800">
<InputGroup @name="Traffic-Aware ETA" @helpText="Use traffic-aware provider calculations when supported.">
<Toggle @isToggled={{this.trackingTrafficEnabled}} @onToggle={{fn (mut this.trackingTrafficEnabled)}} />
</InputGroup>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<InputGroup @name="Summary Cache TTL">
<Input @type="number" @value={{this.trackingCacheTtlSeconds}} min="15" max="300" class="form-input w-full" />
</InputGroup>
<InputGroup @name="Route Cache TTL">
<Input @type="number" @value={{this.trackingRouteCacheTtlSeconds}} min="60" max="1800" class="form-input w-full" />
</InputGroup>
<InputGroup @name="Stale Location Threshold">
<Input @type="number" @value={{this.trackingStaleLocationThresholdSeconds}} min="30" max="3600" class="form-input w-full" />
</InputGroup>
<InputGroup @name="Fallback Speed">
<Input @type="number" @value={{this.trackingDefaultVehicleSpeedKph}} min="1" max="160" class="form-input w-full" />
</InputGroup>
</div>
</div>
{{/if}}
</ContentPanel>

<RegistryYield @registry="fleet-ops:component:admin:routing-settings" as |RegistryComponent|>
<div>
<RegistryComponent />
</div>
</RegistryYield>
</div>

<EmberWormhole @to="next-view-section-subheader-actions">
<Button
@type="primary"
@size="sm"
@icon="save"
@text="Save Changes"
@onClick={{perform this.saveSettings}}
@isLoading={{or this.saveSettings.isRunning this.loadSettings.isRunning}}
@disabled={{this.saveSettings.isRunning}}
/>
</EmberWormhole>
124 changes: 124 additions & 0 deletions addon/components/admin/routing-settings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { inject as service } from '@ember/service';
import { action } from '@ember/object';
import { task } from 'ember-concurrency';

export default class AdminRoutingSettingsComponent extends Component {
@service fetch;
@service notifications;
@tracked trackingProvider = 'google_routes';
@tracked trackingFallbacks = ['osrm', 'calculated'];
@tracked trackingTrafficEnabled = true;
@tracked trackingCacheTtlSeconds = 60;
@tracked trackingRouteCacheTtlSeconds = 600;
@tracked trackingStaleLocationThresholdSeconds = 300;
@tracked trackingDefaultVehicleSpeedKph = 35;
@tracked showTrackingAdvancedSettings = false;
@tracked trackingProviderOptions = [
{ value: 'google_routes', label: 'Google Routes' },
{ value: 'osrm', label: 'OSRM' },
{ value: 'calculated', label: 'Calculated' },
];

constructor() {
super(...arguments);
this.loadSettings.perform();
}

@task *loadSettings() {
try {
const settings = yield this.fetch.get('fleet-ops/settings/admin-tracking-settings');
this.trackingProvider = settings.provider ?? 'google_routes';
this.trackingFallbacks = this.normalizeFallbacks(settings.fallbacks);
this.trackingTrafficEnabled = settings.traffic_enabled ?? true;
this.trackingCacheTtlSeconds = settings.cache_ttl_seconds ?? 60;
this.trackingRouteCacheTtlSeconds = settings.route_cache_ttl_seconds ?? 600;
this.trackingStaleLocationThresholdSeconds = settings.stale_location_threshold_seconds ?? 300;
this.trackingDefaultVehicleSpeedKph = settings.default_vehicle_speed_kph ?? 35;
this.trackingProviderOptions = this.normalizeProviderOptions(settings.providers ?? this.trackingProviderOptions);
} catch (error) {
this.notifications.serverError(error);
}
}

@task *saveSettings() {
try {
yield this.fetch.post('fleet-ops/settings/admin-tracking-settings', this.trackingSettingsPayload);
this.notifications.success('Tracking defaults saved.');
} catch (error) {
this.notifications.serverError(error);
}
}

get trackingSettingsPayload() {
return {
provider: this.trackingProvider,
fallbacks: this.normalizeFallbacks(this.trackingFallbacks),
traffic_enabled: this.trackingTrafficEnabled,
cache_ttl_seconds: Number(this.trackingCacheTtlSeconds) || 60,
route_cache_ttl_seconds: Number(this.trackingRouteCacheTtlSeconds) || 600,
stale_location_threshold_seconds: Number(this.trackingStaleLocationThresholdSeconds) || 300,
default_vehicle_speed_kph: Number(this.trackingDefaultVehicleSpeedKph) || 35,
};
}

normalizeFallbacks(fallbacks) {
if (Array.isArray(fallbacks)) {
return fallbacks
.map((fallback) => this.optionValue(fallback))
.map((fallback) => String(fallback).trim())
.filter(Boolean);
}

return String(fallbacks ?? '')
.split(',')
.map((fallback) => fallback.trim())
.filter(Boolean);
}

get selectedTrackingFallbackOptions() {
const selected = new Set(this.normalizeFallbacks(this.trackingFallbacks));

return this.trackingProviderOptions.filter((option) => selected.has(this.optionValue(option)));
}

normalizeProviderOptions(options = []) {
return options.map((option) => {
const value = this.optionValue(option);
const label = option?.label ?? option?.name ?? this.providerLabel(value);

return {
...option,
key: option?.key ?? value,
name: option?.name ?? label,
value,
label,
};
});
}

providerLabel(value) {
if (value === 'osrm') {
return 'OSRM';
}

return String(value ?? '')
.replace(/[_-]+/g, ' ')
.replace(/\s+/g, ' ')
.trim()
.replace(/\b\w/g, (character) => character.toUpperCase());
}

optionValue(option) {
return typeof option === 'object' && option !== null ? (option.value ?? option.key) : option;
}

@action setTrackingFallbacks(options) {
this.trackingFallbacks = this.normalizeFallbacks(options);
}

@action toggleTrackingAdvancedSettings() {
this.showTrackingAdvancedSettings = !this.showTrackingAdvancedSettings;
}
}
2 changes: 1 addition & 1 deletion addon/components/customer/orders.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ export default class CustomerOrdersComponent extends Component {
// start loading order tracking activity
order.loadTrackingActivity();
this.urlSearchParams.addParamToCurrentUrl('order', order.public_id);
const driverCurrentLocation = order.get('tracker_data.driver_current_location');
const driverCurrentLocation = order.get('tracker_data.driver.location');
if (driverCurrentLocation) {
this.latitude = driverCurrentLocation.coordinates[1];
this.longitude = driverCurrentLocation.coordinates[0];
Expand Down
2 changes: 1 addition & 1 deletion addon/components/display-place.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
</span>
</div>
{{else}}
{{#if (and @waypointActions (not @hideBadges))}}
{{#if @waypointActions}}
<div class="flex flex-row item-center space-x-2 {{if (or this.place.status_code @eta) 'mb-2'}}">
{{#unless @hideBadges}}
{{#if this.place.status_code}}
Expand Down
21 changes: 18 additions & 3 deletions addon/components/map/order-list-overlay/order.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,25 @@
<div class="order-listing-row-progress">
<OrderProgressBar
@order={{@order}}
@progress={{@order.tracker_data.progress_percentage}}
@firstWaypointCompleted={{@order.tracker_data.first_waypoint_completed}}
@lastWaypointCompleted={{@order.tracker_data.last_waypoint_completed}}
@progress={{@order.tracker_data.progress.percentage}}
@firstWaypointCompleted={{gt @order.tracker_data.progress.completed_stops 0}}
@lastWaypointCompleted={{eq @order.tracker_data.progress.percentage 100}}
/>
{{#if @order.tracker_data.insights.is_location_stale}}
<div class="flex mt-1">
<Badge @status="warning" @hideStatusDot={{true}}>Stale GPS</Badge>
</div>
{{else if @order.tracker_data.fallback_provider}}
<div class="flex mt-1">
<Badge @status="warning" @hideStatusDot={{true}}>Fallback ETA</Badge>
</div>
{{else if @order.tracker_data.confidence}}
{{#unless (eq @order.tracker_data.confidence "high")}}
<div class="flex mt-1">
<Badge @status="warning" @hideStatusDot={{true}}>Low ETA Confidence</Badge>
</div>
{{/unless}}
{{/if}}
</div>
<div class="order-listing-row-body">
<div class="order-listing-row-body-address start">
Expand Down
4 changes: 2 additions & 2 deletions addon/components/map/toolbar.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@
<button type="button" id="map-toolbar-view-orders-button" class="toolbar-button relative {{if unauthorized 'unauthorized'}}" {{on "click" this.orderListOverlay.toggle}}>
<div class="active-orders-count absolute top-0 right-0 -mr-2.5 -mt-1">
<div class="relative flex w-5 h-5 rounded-full">
<div class="absolute inset-0 rounded-full w-5 h-5 text-xs bg-blue-500 opacity-75 {{if this.activeOrderCount 'animate-ping'}}"></div>
<div class="relative inline-flex items-center justify-center rounded-full w-5 h-5 text-xs bg-blue-500 text-white z-10">{{this.activeOrderCount}}</div>
<div class="absolute inset-0 rounded-full w-5 h-5 text-xs bg-blue-500 opacity-75 {{if this.displayedActiveOrderCount 'animate-ping'}}"></div>
<div class="relative inline-flex items-center justify-center rounded-full w-5 h-5 text-xs bg-blue-500 text-white z-10">{{this.displayedActiveOrderCount}}</div>
</div>
</div>
<FaIcon @icon="layer-group" />
Expand Down
12 changes: 10 additions & 2 deletions addon/components/map/toolbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,18 @@ export default class MapToolbarComponent extends Component {
return this.mapManager.zoomOut?.();
}

get displayedActiveOrderCount() {
if (this.orderListOverlay.isOpen && this.orderListOverlay.loaded) {
return this.orderListOverlay.activeOrdersCount;
}

return this.activeOrderCount;
}

@task *getActiveOrderCount() {
try {
const count = yield this.fetch.get('fleet-ops/metrics', { discover: ['orders_in_progress'] });
this.activeOrderCount = count.orders_in_progress;
const count = yield this.fetch.get('fleet-ops/metrics', { discover: ['active_live_orders'] });
this.activeOrderCount = count.active_live_orders;
return count;
} catch (err) {
debug('Failed to get active order count: ' + err.message);
Expand Down
21 changes: 18 additions & 3 deletions addon/components/order-list-overlay/order.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,25 @@
<div class="order-listing-row-progress">
<OrderProgressBar
@order={{@order}}
@progress={{@order.tracker_data.progress_percentage}}
@firstWaypointCompleted={{@order.tracker_data.first_waypoint_completed}}
@lastWaypointCompleted={{@order.tracker_data.last_waypoint_completed}}
@progress={{@order.tracker_data.progress.percentage}}
@firstWaypointCompleted={{gt @order.tracker_data.progress.completed_stops 0}}
@lastWaypointCompleted={{eq @order.tracker_data.progress.percentage 100}}
/>
{{#if @order.tracker_data.insights.is_location_stale}}
<div class="flex mt-1">
<Badge @status="warning" @hideStatusDot={{true}}>Stale GPS</Badge>
</div>
{{else if @order.tracker_data.fallback_provider}}
<div class="flex mt-1">
<Badge @status="warning" @hideStatusDot={{true}}>Fallback ETA</Badge>
</div>
{{else if @order.tracker_data.confidence}}
{{#unless (eq @order.tracker_data.confidence "high")}}
<div class="flex mt-1">
<Badge @status="warning" @hideStatusDot={{true}}>Low ETA Confidence</Badge>
</div>
{{/unless}}
{{/if}}
</div>
<div class="order-listing-row-body">
<div class="order-listing-row-body-address start">
Expand Down
27 changes: 21 additions & 6 deletions addon/components/order-progress-card.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,26 @@
<div class="order-progress-card-progress">
<OrderProgressBar
@order={{this.order}}
@progress={{this.order.tracker_data.progress_percentage}}
@firstWaypointCompleted={{this.order.tracker_data.first_waypoint_completed}}
@lastWaypointCompleted={{this.order.tracker_data.last_waypoint_completed}}
@progress={{this.order.tracker_data.progress.percentage}}
@firstWaypointCompleted={{gt this.order.tracker_data.progress.completed_stops 0}}
@lastWaypointCompleted={{eq this.order.tracker_data.progress.percentage 100}}
/>
</div>
{{#if this.order.tracker_data.insights.is_location_stale}}
<div class="flex px-1 mt-1">
<Badge @status="warning" @hideStatusDot={{true}}>Stale GPS</Badge>
</div>
{{else if this.order.tracker_data.fallback_provider}}
<div class="flex px-1 mt-1">
<Badge @status="warning" @hideStatusDot={{true}}>Fallback ETA</Badge>
</div>
{{else if this.order.tracker_data.confidence}}
{{#unless (eq this.order.tracker_data.confidence "high")}}
<div class="flex px-1 mt-1">
<Badge @status="warning" @hideStatusDot={{true}}>Low ETA Confidence</Badge>
</div>
{{/unless}}
{{/if}}
<div class="order-progress-card-details">
<div class="order-progress-card-details-address start">
{{#if this.order.payload.isMultiDrop}}
Expand All @@ -41,11 +56,11 @@
<div class="grid grid-cols-2 gap-2 mt-1 px-1">
<div class="flex flex-col text-xs">
<div class="font-semibold">{{t "order.fields.current-eta"}}:</div>
<div class="">{{format-duration this.order.tracker_data.current_destination_eta}}</div>
<div class="">{{format-duration this.order.tracker_data.eta.active_stop_seconds}}</div>
</div>
<div class="flex flex-col text-xs">
<div class="font-semibold">{{t "order.fields.ect"}}:</div>
<div class="">{{n-a this.order.tracker_data.estimated_completion_time_formatted}}</div>
<div class="">{{n-a this.order.tracker_data.eta.completion_at}}</div>
</div>
<div class="flex flex-col text-xs">
<div class="font-semibold">{{t "order.fields.driver"}}:</div>
Expand All @@ -57,7 +72,7 @@
</div>
<div class="flex flex-col text-xs">
<div class="font-semibold">{{t "order.fields.current-destination"}}:</div>
<div class="truncate">{{n-a this.order.tracker_data.current_destination.address}}</div>
<div class="truncate">{{n-a this.order.tracker_data.active_stop.address}}</div>
</div>
</div>
</div>
Expand Down
Loading
Loading