-
{{foodTruck.vehicle.display_name}}
+
+
{{n-a foodTruck.service_area.name}}
diff --git a/addon/templates/networks/index/network/index.hbs b/addon/templates/networks/index/network/index.hbs
index 4666f10b..e0d28ae5 100644
--- a/addon/templates/networks/index/network/index.hbs
+++ b/addon/templates/networks/index/network/index.hbs
@@ -139,6 +139,7 @@
- {{model.name}}
+
diff --git a/addon/templates/promotions/push-notifications.hbs b/addon/templates/promotions/push-notifications.hbs
index b74e9994..35f04270 100644
--- a/addon/templates/promotions/push-notifications.hbs
+++ b/addon/templates/promotions/push-notifications.hbs
@@ -37,6 +37,7 @@
-
-
-
-
-
-
{{model.name}}
-
{{n-a model.email}}
-
{{n-a model.phone}}
-
-
+
{{/unless}}
diff --git a/addon/templates/settings/index.hbs b/addon/templates/settings/index.hbs
index 581dec48..3e3ee253 100644
--- a/addon/templates/settings/index.hbs
+++ b/addon/templates/settings/index.hbs
@@ -217,6 +217,7 @@
- {{model.name}}
+
diff --git a/package.json b/package.json
index 43ea033e..344a57db 100644
--- a/package.json
+++ b/package.json
@@ -46,7 +46,7 @@
"@babel/core": "^7.23.2",
"@fleetbase/ember-core": "^0.3.25",
"@fleetbase/ember-ui": "^0.4.5",
- "@fleetbase/fleetops-data": "^0.1.37",
+ "@fleetbase/fleetops-data": "^0.2.3",
"@fortawesome/ember-fontawesome": "^2.0.0",
"@fortawesome/fontawesome-svg-core": "6.4.0",
"@fortawesome/free-brands-svg-icons": "6.4.0",
diff --git a/tests/integration/components/shared-identity-test.js b/tests/integration/components/shared-identity-test.js
new file mode 100644
index 00000000..00bf135c
--- /dev/null
+++ b/tests/integration/components/shared-identity-test.js
@@ -0,0 +1,71 @@
+import { module, test } from 'qunit';
+import { setupRenderingTest } from 'dummy/tests/helpers';
+import { render } from '@ember/test-helpers';
+import { hbs } from 'ember-cli-htmlbars';
+import { initialize } from '@fleetbase/fleetops-data/instance-initializers/register-shared-resource-descriptors';
+
+/**
+ * Storefront never loads the FleetOps engine to show a driver, customer or
+ * place: the descriptors and wrappers come from fleetops-data and the
+ * generic components from ember-ui, both of which this engine bundles.
+ */
+module('Integration | Component | shared identity components', function (hooks) {
+ setupRenderingTest(hooks);
+
+ hooks.beforeEach(function () {
+ initialize(this.owner);
+ });
+
+ test('a driver identity cell renders the name, status dot and vehicle badge from the shared descriptor', async function (assert) {
+ this.set('row', { driver_assigned: { resourceType: 'driver', name: 'Ada Driver', status: 'available', vehicle_name: 'Truck 10' } });
+ this.set('column', { resourceType: 'driver', resourcePath: 'driver_assigned', popover: false });
+
+ await render(hbs`
`);
+
+ assert.dom('[data-test-identity-label]').hasText('Ada Driver');
+ assert.dom('[data-test-resource-identity-status-dot]').hasClass('text-green-500');
+ assert.dom('[data-test-resource-identity-meta-badge][data-badge-key="vehicle"]').hasText('Truck 10');
+ });
+
+ test('an empty relation renders the column empty text instead of a cell', async function (assert) {
+ this.set('row', {});
+ this.set('column', { resourceType: 'driver', resourcePath: 'driver_assigned', emptyText: 'No driver assigned' });
+
+ await render(hbs`
`);
+
+ assert.dom('[data-test-identity-empty-text]').hasText('No driver assigned');
+ assert.dom('[data-test-identity-cell]').doesNotExist();
+ });
+
+ test('the customer, driver, place and vehicle pills and select options resolve inside storefront', async function (assert) {
+ this.set('customer', { resourceType: 'customer', name: 'Ava Chen', customer_type: 'fleet-ops:contact' });
+ this.set('driver', { resourceType: 'driver', name: 'Ada Driver', phone: '+15550100' });
+ this.set('place', { resourceType: 'place', name: 'Depot', city: 'Singapore', country: 'SG' });
+ this.set('vehicle', { resourceType: 'vehicle', displayName: 'Truck 10', plate_number: 'TRK-10' });
+
+ await render(hbs`
+
+
+
+
+
+ `);
+
+ assert.dom('[data-test-customer] [data-test-resource-pill-title]').hasText('Ava Chen');
+ assert.dom('[data-test-customer] [data-test-resource-pill-subtitle]').hasText('Customer');
+ assert.dom('[data-test-driver] [data-test-resource-pill-title]').hasText('Ada Driver');
+ assert.dom('[data-test-driver] [data-test-resource-pill-subtitle]').hasText('+15550100');
+ assert.dom('[data-test-place] [data-test-resource-pill-title]').hasText('Depot');
+ assert.dom('[data-test-place] [data-test-resource-pill-subtitle]').hasText('Singapore, SG');
+ assert.dom('[data-test-vehicle] [data-test-select-option-title]').hasText('Truck 10');
+ assert.dom('[data-test-vehicle] .select-option').hasClass('select-option--compact');
+ assert.dom('[data-test-driver-option] [data-test-select-option-title]').hasText('Ada Driver');
+ });
+
+ test('a pill with no record shows its fallback title and does not open anything', async function (assert) {
+ await render(hbs`
`);
+
+ assert.dom('[data-test-resource-pill-title]').hasText('No driver assigned');
+ assert.dom('[data-test-resource-pill] a[href]').doesNotExist();
+ });
+});
diff --git a/tests/integration/components/storefront/order/details-test.js b/tests/integration/components/storefront/order/details-test.js
index 1d392fca..981b8944 100644
--- a/tests/integration/components/storefront/order/details-test.js
+++ b/tests/integration/components/storefront/order/details-test.js
@@ -2,10 +2,15 @@ import { module, test } from 'qunit';
import { setupRenderingTest } from 'dummy/tests/helpers';
import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
+import { initialize } from '@fleetbase/fleetops-data/instance-initializers/register-shared-resource-descriptors';
module('Integration | Component | storefront/order/details', function (hooks) {
setupRenderingTest(hooks);
+ hooks.beforeEach(function () {
+ initialize(this.owner);
+ });
+
test('it renders dedicated storefront order detail panels', async function (assert) {
this.set('order', {
public_id: 'order_test',
@@ -58,9 +63,30 @@ module('Integration | Component | storefront/order/details', function (hooks) {
assert.dom('.next-content-panel-title-container').hasTextContaining('Route');
assert.dom('.next-content-panel-title-container').hasTextContaining('Metadata');
assert.dom('.storefront-order-person__name').hasTextContaining('Tasty Store');
+ assert.dom('[data-test-order-driver-pill] [data-test-resource-pill-title]').hasText('No driver assigned', 'an unassigned driver is a static pill with the fallback title');
+ assert.dom('[data-test-order-customer-pill] [data-test-resource-pill-title]').hasText('No customer');
assert.dom('.storefront-order-item__image').exists();
assert.dom('.storefront-order-item__price').exists();
assert.dom().doesNotContainText('Delivery Address');
assert.dom().doesNotContainText('Pickup Address');
});
+
+ test('a loaded customer and driver render as their pills', async function (assert) {
+ this.set('order', {
+ public_id: 'order_test',
+ meta: { total: 10, currency: 'USD', is_pickup: true },
+ customer: { id: 'contact_1', resourceType: 'customer', name: 'Ava Chen', email: 'ava@example.test', phone: '+15550201' },
+ driver_assigned: { id: 'driver_1', resourceType: 'driver', name: 'Ada Driver', phone: '+15550100' },
+ payload: { pickup: {}, dropoff: {}, entities: [] },
+ tracking_number: {},
+ tracking_statuses: [],
+ files: [],
+ });
+
+ await render(hbs`
`);
+
+ assert.dom('[data-test-order-customer-pill] [data-test-resource-pill-title]').hasText('Ava Chen');
+ assert.dom('[data-test-order-driver-pill] [data-test-resource-pill-title]').hasText('Ada Driver');
+ assert.dom('[data-test-order-driver-pill] [data-test-resource-pill-subtitle]').hasText('+15550100');
+ });
});
diff --git a/tests/integration/components/widget/customers-test.js b/tests/integration/components/widget/customers-test.js
index 9d51d239..4aa03639 100644
--- a/tests/integration/components/widget/customers-test.js
+++ b/tests/integration/components/widget/customers-test.js
@@ -57,7 +57,7 @@ module('Integration | Component | widget/customers', function (hooks) {
assert.dom('.storefront-widget-count').hasText('1');
assert.dom('.storefront-customers-table').exists();
assert.dom('.storefront-customer-id').hasText('contact_1');
- assert.dom('.storefront-customers-table').includesText('Ava Chen');
+ assert.dom('.storefront-customers-table [data-test-resource-pill-title]').hasText('Ava Chen', 'the customer renders as its pill');
assert.dom('.storefront-customers-table').includesText('ava.chen@example.test');
});
});
diff --git a/tests/unit/controllers/customers/index-test.js b/tests/unit/controllers/customers/index-test.js
index 1acfcb49..d8189724 100644
--- a/tests/unit/controllers/customers/index-test.js
+++ b/tests/unit/controllers/customers/index-test.js
@@ -17,4 +17,13 @@ module('Unit | Controller | customers/index', function (hooks) {
assert.true(nameColumn.sticky, 'name column is sticky');
assert.strictEqual(actionColumn.sticky, 'right', 'action column is sticky on the right');
});
+
+ test('the name column is a customer identity cell that opens the storefront customer', function (assert) {
+ let controller = this.owner.lookup('controller:customers/index');
+ let [nameColumn] = controller.columns;
+
+ assert.strictEqual(nameColumn.cellComponent, 'table/cell/identity');
+ assert.strictEqual(nameColumn.resourceType, 'customer');
+ assert.strictEqual(nameColumn.action, controller.viewCustomer);
+ });
});
diff --git a/tests/unit/controllers/networks/index/network/customers-test.js b/tests/unit/controllers/networks/index/network/customers-test.js
index 844ec516..bd90c9fb 100644
--- a/tests/unit/controllers/networks/index/network/customers-test.js
+++ b/tests/unit/controllers/networks/index/network/customers-test.js
@@ -4,9 +4,17 @@ import { setupTest } from 'dummy/tests/helpers';
module('Unit | Controller | networks/index/network/customers', function (hooks) {
setupTest(hooks);
- // TODO: Replace this with your real tests.
test('it exists', function (assert) {
let controller = this.owner.lookup('controller:networks/index/network/customers');
assert.ok(controller);
});
+
+ test('the name column is a customer identity cell that opens the customer panel', function (assert) {
+ let controller = this.owner.lookup('controller:networks/index/network/customers');
+ let [nameColumn] = controller.columns;
+
+ assert.strictEqual(nameColumn.cellComponent, 'table/cell/identity');
+ assert.strictEqual(nameColumn.resourceType, 'customer');
+ assert.strictEqual(nameColumn.action, controller.viewCustomer);
+ });
});
diff --git a/tests/unit/controllers/networks/index/network/orders-test.js b/tests/unit/controllers/networks/index/network/orders-test.js
index 59ef20f3..141a91cc 100644
--- a/tests/unit/controllers/networks/index/network/orders-test.js
+++ b/tests/unit/controllers/networks/index/network/orders-test.js
@@ -4,9 +4,28 @@ import { setupTest } from 'dummy/tests/helpers';
module('Unit | Controller | networks/index/network/orders', function (hooks) {
setupTest(hooks);
- // TODO: Replace this with your real tests.
test('it exists', function (assert) {
let controller = this.owner.lookup('controller:networks/index/network/orders');
assert.ok(controller);
});
+
+ test('customer, pickup, dropoff and driver are identity cells typed for the shared descriptors', function (assert) {
+ const controller = this.owner.lookup('controller:networks/index/network/orders');
+ const byId = Object.fromEntries(controller.columns.map((column) => [column.id, column]));
+
+ for (const [id, resourceType] of [
+ ['customer-name', 'customer'],
+ ['pickup-name', 'place'],
+ ['dropoff-name', 'place'],
+ ['driver-assigned', 'driver'],
+ ]) {
+ assert.strictEqual(byId[id].cellComponent, 'table/cell/identity', `${id} is an identity cell`);
+ assert.strictEqual(byId[id].resourceType, resourceType, `${id} is a ${resourceType}`);
+ assert.strictEqual(typeof byId[id].resourcePath, 'function', `${id} resolves its resource`);
+ }
+
+ const driver = { id: 'driver_1' };
+ assert.strictEqual(byId['driver-assigned'].resourcePath({ driver_assigned: driver }), driver);
+ assert.strictEqual(byId['driver-assigned'].resourcePath({ driver_name: 'Bob' }).name, 'Bob');
+ });
});
diff --git a/tests/unit/controllers/orders/index-test.js b/tests/unit/controllers/orders/index-test.js
index b471c95b..6c9ca5d4 100644
--- a/tests/unit/controllers/orders/index-test.js
+++ b/tests/unit/controllers/orders/index-test.js
@@ -1,5 +1,6 @@
import { module, test } from 'qunit';
import { setupTest } from 'dummy/tests/helpers';
+import Service from '@ember/service';
module('Unit | Controller | orders/index', function (hooks) {
setupTest(hooks);
@@ -17,4 +18,69 @@ module('Unit | Controller | orders/index', function (hooks) {
assert.true(idColumn.sticky, 'id column is sticky');
assert.strictEqual(actionColumn.sticky, 'right', 'action column is sticky on the right');
});
+
+ test('customer, pickup, dropoff and driver are identity cells typed for the shared descriptors', function (assert) {
+ const controller = this.owner.lookup('controller:orders/index');
+ const byId = Object.fromEntries(controller.columns.map((column) => [column.id, column]));
+
+ assert.strictEqual(byId['customer-name'].cellComponent, 'table/cell/identity');
+ assert.strictEqual(byId['customer-name'].resourceType, 'customer');
+ assert.strictEqual(byId['pickup-name'].cellComponent, 'table/cell/identity');
+ assert.strictEqual(byId['pickup-name'].resourceType, 'place');
+ assert.strictEqual(byId['dropoff-name'].cellComponent, 'table/cell/identity');
+ assert.strictEqual(byId['dropoff-name'].resourceType, 'place');
+ assert.strictEqual(byId['driver-assigned'].cellComponent, 'table/cell/identity');
+ assert.strictEqual(byId['driver-assigned'].resourceType, 'driver');
+ assert.strictEqual(byId['driver-assigned'].emptyText, 'No driver assigned');
+ });
+
+ test('identity cells read the loaded relation, or a stub named after the row', async function (assert) {
+ const controller = this.owner.lookup('controller:orders/index');
+ const byId = Object.fromEntries(controller.columns.map((column) => [column.id, column]));
+ const customer = { id: 'contact_1', name: 'Ada' };
+ const driver = { id: 'driver_1', name: 'Bob' };
+ const pickup = { id: 'place_1', name: 'Depot' };
+
+ assert.strictEqual(byId['customer-name'].resourcePath({ customer }), customer);
+ assert.strictEqual(byId['driver-assigned'].resourcePath({ driver_assigned: driver }), driver);
+ assert.strictEqual(byId['pickup-name'].resourcePath({ payload: { pickup } }), pickup);
+
+ const customerStub = byId['customer-name'].resourcePath({ customer_name: 'Ada', customer_type: 'fleet-ops:contact' });
+ assert.strictEqual(customerStub.name, 'Ada');
+ assert.strictEqual(customerStub.resourceType, 'fleet-ops:contact');
+ assert.true(customerStub.isIdentityStub);
+
+ const placeStub = byId['dropoff-name'].resourcePath({ dropoffName: '1 Main St' });
+ assert.strictEqual(placeStub.name, '1 Main St');
+ assert.strictEqual(placeStub.resourceType, 'place');
+
+ const found = { id: 'driver_9' };
+ const findRecord = (modelName, id) => (modelName === 'driver' && id === 'driver_9' ? Promise.resolve(found) : Promise.resolve(null));
+ controller.store = { findRecord };
+ const driverStub = byId['driver-assigned'].resourcePath({ driver_name: 'Bob', driver_assigned_uuid: 'driver_9' });
+ assert.strictEqual(driverStub.name, 'Bob');
+ assert.strictEqual(await driverStub.loadResource(), found, 'the stub loads the driver by uuid on demand');
+ assert.strictEqual(await byId['driver-assigned'].resourcePath({ driver_name: 'Bob' }).loadResource(), null, 'no uuid, nothing to load');
+
+ assert.strictEqual(byId['driver-assigned'].resourcePath({}), null, 'no driver at all renders the empty text');
+ });
+
+ test('clicking a customer opens the storefront customer page, and a name-only stub opens nothing', function (assert) {
+ const transitions = [];
+ this.owner.register(
+ 'service:hostRouter',
+ class extends Service {
+ transitionTo(...args) {
+ transitions.push(args);
+ }
+ }
+ );
+ const controller = this.owner.lookup('controller:orders/index');
+
+ controller.viewCustomer({ public_id: 'contact_1', name: 'Ada' });
+ controller.viewCustomer({ name: 'Ada', isIdentityStub: true });
+ controller.viewCustomer(null);
+
+ assert.deepEqual(transitions, [['console.storefront.customers.index.view', 'contact_1']]);
+ });
});
diff --git a/translations/ru-ru.yaml b/translations/ru-ru.yaml
index 20a565f1..d815e0f5 100644
--- a/translations/ru-ru.yaml
+++ b/translations/ru-ru.yaml
@@ -417,8 +417,8 @@ storefront:
enable-tips: Включить чаевые
enable-delivery-tips: Включить чаевые за доставку
enable-multi-cart-checkout: Включить оформление заказа из нескольких
- split-tips-across-stores: "Распределять чаевые между магазинами в заказах из нескольких магазинов"
корзин
+ split-tips-across-stores: "Распределять чаевые между магазинами в заказах из нескольких магазинов"
enable-user-reviews: Включить отзывы пользователей
api-panel:
panel-title: API