Skip to content
Merged
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
24 changes: 24 additions & 0 deletions addon/components/entity/form.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,30 @@
<div class="grid grid-cols-1 lg:grid-cols-2 gap-2">
<InputGroup @name={{t "common.name"}} @value={{@resource.name}} @helpText={{t "modals.entity-form.name-text"}} />
<InputGroup @name={{t "common.internal-id"}} @value={{@resource.internal_id}} @helpText={{t "modals.entity-form.id-text"}} />
<div class="space-y-2">
<InputGroup @name={{t "common.type"}} @helpText="Choose a common entity type or switch to a custom value.">
{{#if this.useCustomType}}
<Input @value={{@resource.type}} @type="text" class="w-full form-input" placeholder="Enter custom entity type" />
{{else}}
<div class="fleetbase-model-select fleetbase-power-select ember-model-select">
<PowerSelect
@options={{this.entityTypes}}
@selected={{this.selectedEntityType}}
@onChange={{this.selectEntityType}}
@placeholder="Select entity type"
@triggerClass="form-select"
as |type|
>
<div class="text-sm">
<div class="font-semibold normalize-in-trigger">{{type.label}}</div>
<div class="hide-from-trigger">{{type.description}}</div>
</div>
</PowerSelect>
</div>
{{/if}}
</InputGroup>
<Checkbox @value={{this.useCustomType}} @label="Use custom type" @onToggle={{this.toggleCustomType}} @alignItems="center" @labelClass="mb-0i" />
</div>
<InputGroup @name={{t "modals.entity-form.sku"}} @value={{@resource.sku}} @helpText={{t "modals.entity-form.sku-text"}} />
<div></div>
<InputGroup @name={{t "modals.entity-form.description"}} @helpText={{t "modals.entity-form.description-text"}} @wrapperClass="col-span-2">
Expand Down
30 changes: 30 additions & 0 deletions addon/components/entity/form.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,41 @@
import Component from '@glimmer/component';
import { inject as service } from '@ember/service';
import { action } from '@ember/object';
import { tracked } from '@glimmer/tracking';
import { task } from 'ember-concurrency';
import fleetOpsOptions from '../../utils/fleet-ops-options';

export default class EntityFormComponent extends Component {
@service fetch;
@service currentUser;
@service notifications;
@tracked useCustomType = false;

constructor() {
super(...arguments);
this.useCustomType = this.selectedEntityType === undefined && Boolean(this.args.resource?.type);
}

get entityTypes() {
return fleetOpsOptions('entityTypes');
}

get selectedEntityType() {
return this.entityTypes.find((option) => option.value === this.args.resource?.type);
}

@action selectEntityType(option) {
this.useCustomType = false;
this.args.resource.type = option?.value ?? null;
}

@action toggleCustomType(value) {
this.useCustomType = value;

if (!value && !this.selectedEntityType) {
this.args.resource.type = null;
}
}

@task *handlePhotoUpload(file) {
try {
Expand Down
2 changes: 1 addition & 1 deletion addon/components/leaflet-draw-control.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export default class LeafletDrawControl extends BaseLayer {
leafletOptions = ['draw', 'edit', 'remove', 'poly', 'position'];

@computed('leafletEvents.[]', 'args') get usedLeafletEvents() {
const leafletEvents = [...this.leafletEvents, ...Object.values(L.Draw.Event)];
const leafletEvents = [...this.leafletEvents, ...Object.values(L.Draw?.Event ?? {})];
return leafletEvents.filter((eventName) => {
eventName = camelize(eventName.replace(':', ' '));
let methodName = `_${eventName}`;
Expand Down
7 changes: 5 additions & 2 deletions addon/components/map/drawer/geofence-event-listing.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { inject as service } from '@ember/service';
import { action } from '@ember/object';
import { next } from '@ember/runloop';

export default class MapDrawerGeofenceEventListingComponent extends Component {
@service fetch;
Expand All @@ -12,8 +13,10 @@ export default class MapDrawerGeofenceEventListingComponent extends Component {

constructor() {
super(...arguments);
this.geofenceEventBus.subscribe(this.currentUser.companyId);
this.loadRecentEvents();
next(() => {
this.geofenceEventBus.subscribe(this.currentUser.companyId);
this.loadRecentEvents();
});
}

get events() {
Expand Down
2 changes: 1 addition & 1 deletion addon/components/order/form/service-rate.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
@name="serviceQuote"
@changed={{fn (mut @resource.service_quote_uuid)}}
/>
<label for={{serviceQuote.uuid}} class="ml-3 flex-1">{{serviceQuote.public_id}}</label>
<label for={{serviceQuote.uuid}} class="ml-3 flex-1 text-sm">{{serviceQuote.public_id}} ({{serviceQuote.service_rate_name}})</label>
<Badge @hideStatusDot={{true}} @status="info">
{{serviceQuote.request_id}}
</Badge>
Expand Down
12 changes: 6 additions & 6 deletions addon/components/service-rate/details.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,11 @@
</tr>
</thead>
<tbody>
{{#each @resource.rate_fees as |rateFee|}}
{{#each @resource.rateFees as |rateFee|}}
<tr>
<td>{{n-a rateFee.min}}</td>
<td>{{n-a rateFee.max}}</td>
<td>{{format-currency rateFee.fee @resource.currency}}</td>
<td>{{format-currency (f-to-int rateFee.fee) @resource.currency}}</td>
</tr>
{{else}}
<tr>
Expand Down Expand Up @@ -158,7 +158,7 @@
<div class="field-info-container">
<div class="field-name">Algorithm Code</div>
<div class="field-value">
<pre class="bg-gray-100 dark:bg-gray-800 p-3 rounded text-xs overflow-x-auto"><code>{{n-a @resource.algorithm}}</code></pre>
<pre class="bg-gray-100 dark:bg-gray-900 font-mono p-3 rounded text-xs overflow-x-auto"><code class="font-mono text-xs">{{n-a @resource.algorithm}}</code></pre>
</div>
</div>
</ContentPanel>
Expand All @@ -167,7 +167,7 @@
{{#if @resource.isParcelService}}
<ContentPanel @title={{t "service-rate.fields.percel-fee-title"}} @open={{true}} @wrapperClass="bordered-top">
<div class="space-y-2">
{{#each @resource.parcel_fees as |parcelFee|}}
{{#each @resource.parcelFees as |parcelFee|}}
<div class="dark:text-gray-100">
<div class="grid grid-cols-7">
<div class="flex flex-col items-start justify-start pt-3">
Expand Down Expand Up @@ -242,7 +242,7 @@
{{t "service-rate.fields.additional-fee"}}
</div>
<div class="field-value">
{{n-a (format-currency parcelFee.fee @resource.currency)}}
{{n-a (format-currency (f-to-int parcelFee.fee) @resource.currency)}}
</div>
</div>
</div>
Expand Down Expand Up @@ -323,4 +323,4 @@
</ContentPanel>

<CustomField::Yield @subject={{@resource}} @viewMode={{true}} @wrapperClass="bordered-top" />
</div>
</div>
43 changes: 28 additions & 15 deletions addon/components/service-rate/form.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@
</tr>
</thead>
<tbody>
{{#each @resource.rate_fees as |rateFee|}}
{{#each @resource.rateFees as |rateFee|}}
<tr>
<td>
<Input
Expand Down Expand Up @@ -252,25 +252,38 @@
{{else if @resource.isAlgorithm}}
<ContentPanel @title={{t "service-rate.fields.custom-algorithm-title"}} @open={{true}} @wrapperClass="bordered-top">
<InfoBlock class="mb-5">
{{t "service-rate.fields.custom-algorithm-info-message"}}
{{t "service-rate.fields.custom-algorithm-info-second-message"}}
Define a custom formula for this service rate using variables wrapped in a single pair of curly braces.

<div class="block my-4 break-text">
<ul class="list-disc space-y-2 pl-16">
<li class="leading-5"><i>{{t "service-rate.fields.distance-message"}}</i>
{{t "service-rate.fields.distance-continue-message"}}</li>
<li class="leading-5"><i>{{t "service-rate.fields.time-message"}}</i>
{{t "service-rate.fields.time-continue-message"}}</li>
<li class="leading-5"><code>&#123;distance&#125;</code>, <code>&#123;distance_m&#125;</code>, <code>&#123;distance_km&#125;</code>, <code>&#123;distance_mi&#125;</code>: route distance in meters, kilometers, or miles.</li>
<li class="leading-5"><code>&#123;time&#125;</code>, <code>&#123;time_s&#125;</code>, <code>&#123;time_min&#125;</code>: route time in seconds or minutes.</li>
<li class="leading-5"><code>&#123;stops&#125;</code>: total service stops including pickup, dropoff, and intermediate waypoints.</li>
<li class="leading-5"><code>&#123;waypoints&#125;</code>: intermediate waypoint count only.</li>
<li class="leading-5"><code>&#123;parcels&#125;</code>, <code>&#123;entities&#125;</code>: parcel count and total payload entity count.</li>
<li class="leading-5"><code>&#123;base_fee&#125;</code>: the configured base fee for this service rate.</li>
<li class="leading-5">Supported functions: <code>max(a,b)</code>, <code>min(a,b)</code>, <code>ceil(x)</code>, <code>floor(x)</code>, <code>round(x)</code>.</li>
</ul>
</div>

<div>
<h4 class="mb-1 text-sm font-semibold">{{t "service-rate.fields.example"}}</h4>
<div class="mb-3 text-inherit">
{{t "service-rate.fields.example-message"}}
{{t "service-rate.fields.example-second-message"}}
<div class="space-y-3">
<div>
<h4 class="mb-1 text-sm font-semibold">{{t "service-rate.fields.example"}} 1</h4>
<div class="mb-2 text-inherit">Charge by distance in miles after the first 15 miles, plus the base fee.</div>
<code class="text-xs font-mono">max(&#123;distance_mi&#125; - 15, 0) * 5 + &#123;base_fee&#125;</code>
</div>

<div>
<h4 class="mb-1 text-sm font-semibold">{{t "service-rate.fields.example"}} 2</h4>
<div class="mb-2 text-inherit">Charge extra for stops after the first 2 service stops.</div>
<code class="text-xs font-mono">max(&#123;stops&#125; - 2, 0) * 10 + &#123;base_fee&#125;</code>
</div>

<div>
<h4 class="mb-1 text-sm font-semibold">{{t "service-rate.fields.example"}} 3</h4>
<div class="mb-2 text-inherit">Combine waypoint, parcel, and distance surcharges in one formula.</div>
<code class="text-xs font-mono">max(&#123;waypoints&#125;, 0) * 15 + max(&#123;parcels&#125; - 3, 0) * 2 + max(&#123;distance_km&#125; - 25, 0) * 0.5 + &#123;base_fee&#125;</code>
</div>
<code class="text-xs font-mono">(( {distance} / 50 ) * .05 ) + (( {time} / 60 ) * .01)</code>
</div>
</InfoBlock>

Expand All @@ -287,7 +300,7 @@
{{else if @resource.isParcelService}}
<ContentPanel @title={{t "service-rate.fields.percel-fee-title"}} @open={{true}} @wrapperClass="bordered-top">
<div class="space-y-2">
{{#each @resource.parcel_fees as |parcelFee|}}
{{#each @resource.parcelFees as |parcelFee|}}
<div class="dark:text-gray-100">
<div class="grid grid-cols-7">
<div class="flex flex-col items-start justify-start pt-3">
Expand Down Expand Up @@ -541,4 +554,4 @@
</InputGroup>
{{/if}}
</ContentPanel>
</div>
</div>
29 changes: 27 additions & 2 deletions addon/controllers/operations/orders/index/details.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export default class OperationsOrdersIndexDetailsController extends Controller {
@service universe;
@service sidebar;
@tracked routingControl;
@tracked routingCompleted = false;

get tabs() {
const registeredTabs = this.menuService.getMenuItems('fleet-ops:component:order:details');
Expand Down Expand Up @@ -109,7 +110,19 @@ export default class OperationsOrdersIndexDetailsController extends Controller {
@action async setup() {
// Change to map layout and display order route
this.index.changeLayout('map');
this.routingControl = await this.leafletMapManager.addRoutingControl(this.model.routeWaypoints);
this.routingCompleted = false;
this.routingControl = await this.leafletMapManager.addRoutingControl(this.model.routeWaypoints, {
onRouteFound: () => {
this.routingCompleted = true;
},
onRoutingError: () => {
this.routingCompleted = true;
},
});

if (!this.routingControl) {
this.routingCompleted = true;
}

// Hide sidebar
this.sidebar.hideNow();
Expand All @@ -125,7 +138,19 @@ export default class OperationsOrdersIndexDetailsController extends Controller {
async (_msg, { reloadable }) => {
if (reloadable) {
await this.hostRouter.refresh();
this.leafletMapManager.replaceRoutingControl(this.model.routeWaypoints, this.routingControl);
this.routingCompleted = false;
this.routingControl = await this.leafletMapManager.replaceRoutingControl(this.model.routeWaypoints, this.routingControl, {
onRouteFound: () => {
this.routingCompleted = true;
},
onRoutingError: () => {
this.routingCompleted = true;
},
});

if (!this.routingControl) {
this.routingCompleted = true;
}
}
},
{ debounceMs: 250 }
Expand Down
5 changes: 5 additions & 0 deletions addon/controllers/operations/orders/index/details/virtual.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import Controller, { inject as controller } from '@ember/controller';

export default class OperationsOrdersIndexDetailsVirtualController extends Controller {
@controller('operations.orders.index.details') parent;
}
1 change: 1 addition & 0 deletions addon/controllers/operations/service-rates/index/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export default class OperationsServiceRatesIndexEditController extends Controlle
@task *save(serviceRate) {
try {
yield serviceRate.save();
yield serviceRate.reload();
this.events.trackResourceUpdated(serviceRate);
this.overlay?.close();

Expand Down
1 change: 1 addition & 0 deletions addon/controllers/operations/service-rates/index/new.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export default class OperationsServiceRatesIndexNewController extends Controller
@task *save(serviceRate) {
try {
yield serviceRate.save();
yield serviceRate.reload();
this.events.trackResourceCreated(serviceRate);
this.overlay?.close();

Expand Down
6 changes: 6 additions & 0 deletions addon/helpers/f-to-int.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { helper } from '@ember/component/helper';
import numbersOnly from '@fleetbase/ember-core/utils/numbers-only';

export default helper(function fToInt([value]) {
return parseInt(numbersOnly(value));
});
6 changes: 3 additions & 3 deletions addon/routes/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ export default class ApplicationRoute extends Route {
});
}

beforeModel() {
async beforeModel() {
if (this.abilities.cannot('fleet-ops see extension')) {
this.notifications.warning(this.intl.t('common.unauthorized-access'));
return this.hostRouter.transitionTo('console');
}

this.location.getUserLocation();
this.#loadRoutingSettings();
await this.location.getUserLocation();
await this.#loadRoutingSettings();
}

async #loadRoutingSettings() {
Expand Down
22 changes: 19 additions & 3 deletions addon/routes/operations/orders/index/details.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@ export default class OperationsOrdersIndexDetailsRoute extends Route {
@action willTransition(transition) {
const fromName = transition.from?.name;
const toName = transition.to?.name;
const orderDetailsRoute = 'console.fleet-ops.operations.orders.index.details';

// only cleanup when actually leaving this route (not intra-route changes)
if (fromName && fromName !== toName) {
// only cleanup when leaving the order details route tree entirely
const isLeavingOrderDetails = fromName?.startsWith(orderDetailsRoute) && !toName?.startsWith(orderDetailsRoute);

if (isLeavingOrderDetails) {
const controller = this.controllerFor('operations.orders.index.details');
const rc = controller.routingControl;

Expand Down Expand Up @@ -55,7 +58,20 @@ export default class OperationsOrdersIndexDetailsRoute extends Route {
return this.store.queryRecord('order', {
public_id,
single: true,
with: ['payload', 'driverAssigned', 'orderConfig', 'customer', 'facilitator', 'trackingStatuses', 'trackingNumber', 'purchaseRate', 'comments', 'files'],
with: [
'payload',
'driverAssigned',
'orderConfig',
'customer',
'facilitator',
'trackingStatuses',
'trackingNumber',
'purchaseRate',
'purchaseRate.serviceQuote',
'purchaseRate.serviceQuote.items',
'comments',
'files',
],
});
}

Expand Down
1 change: 1 addition & 0 deletions addon/routes/operations/orders/index/details/virtual.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ export default class OperationsOrdersIndexDetailsVirtualRoute extends Route {
setupController(controller) {
super.setupController(...arguments);
controller.resource = this.modelFor('operations.orders.index.details');
controller.detailsController = this.controllerFor('operations.orders.index.details');
}
}
10 changes: 4 additions & 6 deletions addon/services/equipment-actions.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import ResourceActionService, { inject as service } from '@fleetbase/ember-core/services/resource-action';
import ResourceActionService from '@fleetbase/ember-core/services/resource-action';

export default class EquipmentActionsService extends ResourceActionService {
@service currentUser;
get defaultCurrency() {
return this.currentUser?.company?.currency || this.currentUser.currency || 'USD';
}

constructor() {
super(...arguments);
Expand All @@ -12,10 +14,6 @@ export default class EquipmentActionsService extends ResourceActionService {
});
}

get defaultCurrency() {
return this.currentUser?.company?.currency || 'USD';
}

transition = {
view: (equipment) => this.transitionTo('maintenance.equipment.index.details', equipment),
edit: (equipment) => this.transitionTo('maintenance.equipment.index.edit', equipment),
Expand Down
Loading
Loading