From ab662a8507e6a43e594bf3efbef5f4464bd2ddce Mon Sep 17 00:00:00 2001 From: Turtuvshin Date: Tue, 12 Mar 2024 19:22:12 +0800 Subject: [PATCH 01/32] Add customer panel and related components and routes --- addon/components/customer-panel.hbs | 74 ++++ addon/components/customer-panel.js | 165 +++++++++ addon/components/customer-panel/details.hbs | 36 ++ addon/components/customer-panel/details.js | 3 + addon/components/customer-panel/orders.hbs | 1 + addon/components/customer-panel/orders.js | 3 + addon/controllers/customers/index.js | 14 +- addon/controllers/customers/index/view.js | 44 +++ .../networks/index/network/orders.js | 319 +++++++++++++++++- addon/routes.js | 1 + addon/routes/customers/index/view.js | 14 + addon/routes/networks/index/network/orders.js | 7 + addon/templates/customers/index/view.hbs | 1 + .../networks/index/network/orders.hbs | 19 +- app/components/customer-panel.js | 1 + app/components/customer-panel/details.js | 1 + app/components/customer-panel/orders.js | 1 + app/controllers/customers/index/view.js | 1 + app/routes/customers/index/view.js | 1 + app/templates/customers/index/view.js | 1 + .../components/customer-panel-test.js | 26 ++ .../components/customer-panel/details-test.js | 26 ++ .../components/customer-panel/orders-test.js | 26 ++ .../controllers/customers/index/view-test.js | 12 + .../unit/routes/customers/index/view-test.js | 11 + 25 files changed, 801 insertions(+), 7 deletions(-) create mode 100644 addon/components/customer-panel.hbs create mode 100644 addon/components/customer-panel.js create mode 100644 addon/components/customer-panel/details.hbs create mode 100644 addon/components/customer-panel/details.js create mode 100644 addon/components/customer-panel/orders.hbs create mode 100644 addon/components/customer-panel/orders.js create mode 100644 addon/controllers/customers/index/view.js create mode 100644 addon/routes/customers/index/view.js create mode 100644 addon/templates/customers/index/view.hbs create mode 100644 app/components/customer-panel.js create mode 100644 app/components/customer-panel/details.js create mode 100644 app/components/customer-panel/orders.js create mode 100644 app/controllers/customers/index/view.js create mode 100644 app/routes/customers/index/view.js create mode 100644 app/templates/customers/index/view.js create mode 100644 tests/integration/components/customer-panel-test.js create mode 100644 tests/integration/components/customer-panel/details-test.js create mode 100644 tests/integration/components/customer-panel/orders-test.js create mode 100644 tests/unit/controllers/customers/index/view-test.js create mode 100644 tests/unit/routes/customers/index/view-test.js diff --git a/addon/components/customer-panel.hbs b/addon/components/customer-panel.hbs new file mode 100644 index 0000000..cdda5bb --- /dev/null +++ b/addon/components/customer-panel.hbs @@ -0,0 +1,74 @@ + + +
+
+
+
+
+
+
+
+
+
+ {{this.contact.name}} + + + +
+
+

{{this.contact.name}}

+
+
+ {{smart-humanize this.contact.type}} +
+
+
+
+
+
+ +
+
+
+ +
+
+ +
+
+
+ {{component this.tab.component customer=this.customer tabOptions=this.tab options=this.tab.componentParams}} +
+
+
\ No newline at end of file diff --git a/addon/components/customer-panel.js b/addon/components/customer-panel.js new file mode 100644 index 0000000..e4b2eb6 --- /dev/null +++ b/addon/components/customer-panel.js @@ -0,0 +1,165 @@ +import Component from '@glimmer/component'; +import { tracked } from '@glimmer/tracking'; +import { action } from '@ember/object'; +import { inject as service } from '@ember/service'; +import { isArray } from '@ember/array'; +import CustomerPanelDetailsComponent from './customer-panel/details'; +import CustomerPanelOrdersComponent from './customer-panel/orders'; +import contextComponentCallback from '../utils/context-component-callback'; +import applyContextComponentArguments from '../utils/apply-context-component-arguments'; + +export default class CustomerPanelComponent extends Component { + /** + * Service for fetching data. + * + * @type {Service} + */ + @service fetch; + + /** + * Service for managing modals. + * + * @type {Service} + */ + @service modalsManager; + + /** + * Universe service for managing global data and settings. + * + * @type {Service} + */ + @service universe; + + /** + * Ember data store service. + * + * @type {Service} + */ + @service store; + + /** + * Service for managing routing within the host app. + * + * @type {Service} + */ + @service hostRouter; + + /** + * Service for managing the context panel. + * + * @type {Service} + */ + @service contextPanel; + + /** + * The current active tab. + * + * @type {Object} + * @tracked + */ + @tracked tab; + + /** + * The customer being displayed or edited. + * + * @type {customerModel} + * @tracked + */ + @tracked customer; + + /** + * Returns the array of tabs available for the panel. + * + * @type {Array} + */ + get tabs() { + const registeredTabs = this.universe.getMenuItemsFromRegistry('component:customer-panel'); + + const defaultTabs = [ + this.universe._createMenuItem('Details', null, { icon: 'circle-info', component: CustomerPanelDetailsComponent }), + this.universe._createMenuItem('Orders', null, { icon: 'circle-info', component: CustomerPanelOrdersComponent }), + ]; + + if (isArray(registeredTabs)) { + return [...defaultTabs, ...registeredTabs]; + } + + return defaultTabs; + } + + /** + * Initializes the customer panel component. + */ + constructor() { + super(...arguments); + this.customer = this.args.customer; + this.tab = this.getTabUsingSlug(this.args.tab); + applyContextComponentArguments(this); + } + + /** + * Sets the overlay context. + * + * @action + * @param {OverlayContextObject} overlayContext + */ + @action setOverlayContext(overlayContext) { + this.context = overlayContext; + contextComponentCallback(this, 'onLoad', ...arguments); + } + + /** + * Handles changing the active tab. + * + * @method + * @param {String} tab - The new tab to switch to. + * @action + */ + @action onTabChanged(tab) { + this.tab = this.getTabUsingSlug(tab); + contextComponentCallback(this, 'onTabChanged', tab); + } + + /** + * Handles edit action for the customer. + * + * @method + * @action + */ + @action onEdit() { + const isActionOverrided = contextComponentCallback(this, 'onEdit', this.customer); + + if (!isActionOverrided) { + this.contextPanel.focus(this.customer, 'editing', { + onAfterSave: () => { + this.contextPanel.clear(); + }, + }); + } + } + + /** + * Handles the cancel action. + * + * @method + * @action + * @returns {Boolean} Indicates whether the cancel action was overridden. + */ + @action onPressCancel() { + return contextComponentCallback(this, 'onPressCancel', this.customer); + } + + /** + * Finds and returns a tab based on its slug. + * + * @param {String} tabSlug - The slug of the tab. + * @returns {Object|null} The found tab or null. + */ + getTabUsingSlug(tabSlug) { + if (tabSlug) { + return this.tabs.find(({ slug }) => slug === tabSlug); + } + + return this.tabs[0]; + } +} diff --git a/addon/components/customer-panel/details.hbs b/addon/components/customer-panel/details.hbs new file mode 100644 index 0000000..4afc2f2 --- /dev/null +++ b/addon/components/customer-panel/details.hbs @@ -0,0 +1,36 @@ +
+ +
+ +
+
{{t "fleet-ops.component.customer-panel.details.web-url"}}
+
{{n-a @customer.name}}
+
+ +
+
{{t "fleet-ops.common.title"}}
+
{{n-a @customer.title}}
+
+ +
+
{{t "fleet-ops.common.internal-id"}}
+
{{n-a @customer.internal_id}}
+
+ +
+
{{t "fleet-ops.common.email"}}
+
{{n-a @customer.email}}
+
+ +
+
{{t "fleet-ops.common.email"}}
+
{{n-a @customer.phone}}
+
+ +
+
{{t "fleet-ops.common.type"}}
+
+
+
+
+
\ No newline at end of file diff --git a/addon/components/customer-panel/details.js b/addon/components/customer-panel/details.js new file mode 100644 index 0000000..34767bb --- /dev/null +++ b/addon/components/customer-panel/details.js @@ -0,0 +1,3 @@ +import Component from '@glimmer/component'; + +export default class CustomerPanelDetailsComponent extends Component {} diff --git a/addon/components/customer-panel/orders.hbs b/addon/components/customer-panel/orders.hbs new file mode 100644 index 0000000..fb5c4b1 --- /dev/null +++ b/addon/components/customer-panel/orders.hbs @@ -0,0 +1 @@ +{{yield}} \ No newline at end of file diff --git a/addon/components/customer-panel/orders.js b/addon/components/customer-panel/orders.js new file mode 100644 index 0000000..9086618 --- /dev/null +++ b/addon/components/customer-panel/orders.js @@ -0,0 +1,3 @@ +import Component from '@glimmer/component'; + +export default class CustomerPanelOrdersComponent extends Component {} diff --git a/addon/controllers/customers/index.js b/addon/controllers/customers/index.js index 5e87588..d0b5207 100644 --- a/addon/controllers/customers/index.js +++ b/addon/controllers/customers/index.js @@ -1,11 +1,11 @@ -import Controller from '@ember/controller'; import { inject as service } from '@ember/service'; -import { tracked } from '@glimmer/tracking'; import { isBlank } from '@ember/utils'; +import BaseController from '@fleetbase/storefront-engine/controllers/base-controller'; +import { tracked } from '@glimmer/tracking'; import { timeout } from 'ember-concurrency'; import { task } from 'ember-concurrency-decorators'; - -export default class CustomersIndexController extends Controller { +import { action } from '@ember/object'; +export default class CustomersIndexController extends BaseController { /** * Inject the `notifications` service * @@ -226,7 +226,7 @@ export default class CustomersIndexController extends Controller { actions: [ { label: this.intl.t('storefront.customers.index.view-customer-details'), - // fn: this.viewVendor, + fn: this.viewCustomer, }, { label: this.intl.t('storefront.customers.index.edit-customer'), @@ -248,6 +248,10 @@ export default class CustomersIndexController extends Controller { }, ]; + @action viewCustomer(customer) { + return this.transitionToRoute('customers.index.view', customer); + } + /** * The search task. * diff --git a/addon/controllers/customers/index/view.js b/addon/controllers/customers/index/view.js new file mode 100644 index 0000000..3d4c3db --- /dev/null +++ b/addon/controllers/customers/index/view.js @@ -0,0 +1,44 @@ +import Controller from '@ember/controller'; +import BaseController from '@fleetbase/fleetops-engine/controllers/base-controller'; +import { tracked } from '@glimmer/tracking'; +import { action } from '@ember/object'; + +export default class CustomersIndexViewController extends BaseController { + /** + * The currently active view tab ('details' by default). + * + * @type {String} + * @tracked + */ + @tracked view = 'details'; + + /** + * An array of query parameters to be serialized in the URL. + * + * @type {String[]} + * @tracked + */ + @tracked queryParams = ['view']; + + /** + * Transitions back to the "management.contacts.index" route. + * + * @method + * @action + * @returns {Transition} The transition object representing the route change. + */ + @action transitionBack() { + return this.transitionToRoute('management.contacts.index'); + } + + /** + * Updates the active view tab. + * + * @method + * @param {String} tab - The name of the tab to activate. + * @action + */ + @action onTabChanged(tab) { + this.view = tab; + } +} diff --git a/addon/controllers/networks/index/network/orders.js b/addon/controllers/networks/index/network/orders.js index 5df842a..dcdd204 100644 --- a/addon/controllers/networks/index/network/orders.js +++ b/addon/controllers/networks/index/network/orders.js @@ -1,3 +1,320 @@ import Controller from '@ember/controller'; +import { inject as service } from '@ember/service'; +import { tracked } from '@glimmer/tracking'; +import { isBlank } from '@ember/utils'; +import { timeout } from 'ember-concurrency'; +import { task } from 'ember-concurrency-decorators'; -export default class NetworksIndexNetworkOrdersController extends Controller {} +export default class NetworksIndexNetworkOrdersController extends Controller { + /** + * Inject the `notifications` service + * + * @var {Service} + */ + @service notifications; + + /** + * Inject the `intl` service + * + * @var {Service} + */ + @service intl; + + /** + * Inject the `modals-manager` service + * + * @var {Service} + */ + @service modalsManager; + + /** + * Inject the `crud` service + * + * @var {Service} + */ + @service crud; + + /** + * Inject the `fetch` service + * + * @var {Service} + */ + @service fetch; + + /** + * Inject the `filters` service + * + * @var {Service} + */ + @service filters; + + /** + * Queryable parameters for this controller's model + * + * @var {Array} + */ + queryParams = []; + + @tracked page = 1; + @tracked limit; + @tracked query; + @tracked sort = '-created_at'; + @tracked public_id; + @tracked internal_id; + @tracked tracking; + @tracked facilitator; + @tracked customer; + @tracked driver; + @tracked payload; + @tracked pickup; + @tracked dropoff; + @tracked updated_by; + @tracked created_by; + @tracked status; + + @tracked columns = [ + { + label: this.intl.t('storefront.common.id'), + valuePath: 'public_id', + width: '150px', + cellComponent: 'table/cell/link-to', + route: 'orders.index.view', + // onLinkClick: this.viewOrder, + resizable: true, + sortable: true, + filterable: true, + filterComponent: 'filter/string', + }, + { + label: this.intl.t('storefront.orders.index.internal-id'), + valuePath: 'internal_id', + width: '125px', + resizable: true, + sortable: true, + filterable: true, + filterComponent: 'filter/string', + }, + { + label: this.intl.t('storefront.orders.index.customer'), + valuePath: 'customer.name', + cellComponent: 'table/cell/base', + width: '125px', + resizable: true, + sortable: true, + hidden: true, + filterable: true, + filterComponent: 'filter/model', + filterComponentPlaceholder: this.intl.t('storefront.orders.index.select-order-customer'), + filterParam: 'customer', + model: 'customer', + }, + { + label: this.intl.t('storefront.common.pickup'), + valuePath: 'pickupName', + cellComponent: 'table/cell/base', + width: '160px', + resizable: true, + sortable: true, + filterable: true, + filterComponent: 'filter/model', + filterComponentPlaceholder: this.intl.t('storefront.orders.index.select-order-pickup-location'), + filterParam: 'pickup', + model: 'place', + }, + { + label: this.intl.t('storefront.common.dropoff'), + valuePath: 'dropoffName', + cellComponent: 'table/cell/base', + width: '160px', + resizable: true, + sortable: true, + filterable: true, + filterComponent: 'filter/model', + filterComponentPlaceholder: this.intl.t('storefront.orders.index.select-order-dropoff-location'), + filterParam: 'dropoff', + model: 'place', + }, + { + label: this.intl.t('storefront.orders.index.scheduled-at'), + valuePath: 'scheduledAt', + sortParam: 'scheduled_at', + filterParam: 'scheduled_at', + width: '150px', + resizable: true, + sortable: true, + filterable: true, + filterComponent: 'filter/date', + }, + { + label: '# Items', + cellComponent: 'table/cell/base', + valuePath: 'item_count', + resizable: true, + hidden: true, + width: '50px', + }, + { + label: this.intl.t('storefront.orders.index.transaction-total'), + cellComponent: 'table/cell/base', + valuePath: 'transaction_amount', + width: '50px', + resizable: true, + hidden: true, + sortable: true, + }, + { + label: this.intl.t('storefront.orders.index.tracking-number'), + cellComponent: 'table/cell/base', + valuePath: 'tracking_number.tracking_number', + width: '170px', + resizable: true, + sortable: true, + filterable: true, + filterComponent: 'filter/string', + }, + { + label: this.intl.t('storefront.orders.index.driver-assigned'), + cellComponent: 'table/cell/driver-name', + valuePath: 'driver_assigned', + modelPath: 'driver_assigned', + width: '170px', + resizable: true, + sortable: true, + filterable: true, + filterComponent: 'filter/model', + filterComponentPlaceholder: this.intl.t('storefront.orders.index.select-driver-for-order'), + filterParam: 'driver', + model: 'driver', + query: { + // no model, serializer, adapter for relations + without: ['fleets', 'vendor', 'vehicle', 'currentJob'], + }, + }, + { + label: this.intl.t('storefront.common.type'), + cellComponent: 'cell/humanize', + valuePath: 'type', + width: '100px', + resizable: true, + hidden: true, + sortable: true, + }, + { + label: this.intl.t('storefront.common.status'), + valuePath: 'status', + cellComponent: 'table/cell/status', + width: '120px', + resizable: true, + sortable: true, + filterable: true, + filterComponent: 'filter/multi-option', + // filterOptions: this.statusOptions, + }, + { + label: this.intl.t('storefront.orders.index.created-at'), + valuePath: 'createdAt', + sortParam: 'created_at', + filterParam: 'created_at', + width: '140px', + resizable: true, + sortable: true, + filterable: true, + filterComponent: 'filter/date', + }, + { + label: this.intl.t('storefront.orders.index.updated-at'), + valuePath: 'updatedAt', + sortParam: 'updated_at', + filterParam: 'updated_at', + width: '125px', + resizable: true, + sortable: true, + hidden: true, + filterable: true, + filterComponent: 'filter/date', + }, + { + label: this.intl.t('storefront.orders.index.created-by'), + valuePath: 'created_by_name', + width: '125px', + resizable: true, + hidden: true, + filterable: true, + filterComponent: 'filter/model', + filterComponentPlaceholder: 'Select user', + filterParam: 'created_by', + model: 'user', + }, + { + label: this.intl.t('storefront.orders.index.updated-by'), + valuePath: 'updated_by_name', + width: '125px', + resizable: true, + hidden: true, + filterable: true, + filterComponent: 'filter/model', + filterComponentPlaceholder: this.intl.t('storefront.orders.index.select-user'), + filterParam: 'updated_by', + model: 'user', + }, + { + label: '', + cellComponent: 'table/cell/dropdown', + ddButtonText: false, + ddButtonIcon: 'ellipsis-h', + ddButtonIconPrefix: 'fas', + ddMenuLabel: 'Order Actions', + cellClassNames: 'overflow-visible', + wrapperClass: 'flex items-center justify-end mx-2', + width: '12%', + actions: [ + { + label: this.intl.t('storefront.orders.index.view-order'), + icon: 'eye', + fn: this.viewOrder, + }, + { + label: this.intl.t('storefront.orders.index.cancel-order'), + icon: 'ban', + fn: this.cancelOrder, + }, + { + separator: true, + }, + { + label: this.intl.t('storefront.orders.index.delete-order'), + icon: 'trash', + fn: this.deleteOrder, + }, + ], + sortable: false, + filterable: false, + resizable: false, + searchable: false, + }, + ]; + + /** + * The search task. + * + * @void + */ + @task({ restartable: true }) *search({ target: { value } }) { + // if no query don't search + if (isBlank(value)) { + this.query = null; + return; + } + + // timeout for typing + yield timeout(250); + + // reset page for results + if (this.page > 1) { + this.page = 1; + } + + // update the query param + this.query = value; + } +} diff --git a/addon/routes.js b/addon/routes.js index 09b3e04..2ee4283 100644 --- a/addon/routes.js +++ b/addon/routes.js @@ -16,6 +16,7 @@ export default buildRoutes(function () { this.route('customers', function () { this.route('index', { path: '/' }, function () { this.route('edit', { path: '/:public_id' }); + this.route('view', { path: '/:public_id' }); }); }); this.route('orders', function () { diff --git a/addon/routes/customers/index/view.js b/addon/routes/customers/index/view.js new file mode 100644 index 0000000..94612de --- /dev/null +++ b/addon/routes/customers/index/view.js @@ -0,0 +1,14 @@ +import Route from '@ember/routing/route'; +import { inject as service } from '@ember/service'; + +export default class CustomersIndexViewRoute extends Route { + @service store; + + queryParams = { + view: { refreshModel: false }, + }; + + model({ public_id }) { + return this.store.findRecord('contact', public_id); + } +} diff --git a/addon/routes/networks/index/network/orders.js b/addon/routes/networks/index/network/orders.js index b6d4d92..1374bb1 100644 --- a/addon/routes/networks/index/network/orders.js +++ b/addon/routes/networks/index/network/orders.js @@ -1,6 +1,13 @@ import Route from '@ember/routing/route'; import { inject as service } from '@ember/service'; +import { action, set } from '@ember/object'; +import isNestedRouteTransition from '@fleetbase/ember-core/utils/is-nested-route-transition'; export default class NetworksIndexNetworkOrdersRoute extends Route { @service store; + @service storefront; + + model(params) { + return this.store.query('order', { ...params, storefront: this.storefront.getActiveStore('public_id') }); + } } diff --git a/addon/templates/customers/index/view.hbs b/addon/templates/customers/index/view.hbs new file mode 100644 index 0000000..0aad4e7 --- /dev/null +++ b/addon/templates/customers/index/view.hbs @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/addon/templates/networks/index/network/orders.hbs b/addon/templates/networks/index/network/orders.hbs index e2147ca..fe3c092 100644 --- a/addon/templates/networks/index/network/orders.hbs +++ b/addon/templates/networks/index/network/orders.hbs @@ -1 +1,18 @@ -{{outlet}} \ No newline at end of file + + + + + \ No newline at end of file diff --git a/app/components/customer-panel.js b/app/components/customer-panel.js new file mode 100644 index 0000000..728c15a --- /dev/null +++ b/app/components/customer-panel.js @@ -0,0 +1 @@ +export { default } from '@fleetbase/storefront-engine/components/customer-panel'; diff --git a/app/components/customer-panel/details.js b/app/components/customer-panel/details.js new file mode 100644 index 0000000..8627445 --- /dev/null +++ b/app/components/customer-panel/details.js @@ -0,0 +1 @@ +export { default } from '@fleetbase/storefront-engine/components/customer-panel/details'; diff --git a/app/components/customer-panel/orders.js b/app/components/customer-panel/orders.js new file mode 100644 index 0000000..ac43c06 --- /dev/null +++ b/app/components/customer-panel/orders.js @@ -0,0 +1 @@ +export { default } from '@fleetbase/storefront-engine/components/customer-panel/orders'; diff --git a/app/controllers/customers/index/view.js b/app/controllers/customers/index/view.js new file mode 100644 index 0000000..863db8c --- /dev/null +++ b/app/controllers/customers/index/view.js @@ -0,0 +1 @@ +export { default } from '@fleetbase/storefront-engine/controllers/customers/index/view'; diff --git a/app/routes/customers/index/view.js b/app/routes/customers/index/view.js new file mode 100644 index 0000000..74e2c04 --- /dev/null +++ b/app/routes/customers/index/view.js @@ -0,0 +1 @@ +export { default } from '@fleetbase/storefront-engine/routes/customers/index/view'; diff --git a/app/templates/customers/index/view.js b/app/templates/customers/index/view.js new file mode 100644 index 0000000..60f8389 --- /dev/null +++ b/app/templates/customers/index/view.js @@ -0,0 +1 @@ +export { default } from '@fleetbase/storefront-engine/templates/customers/index/view'; diff --git a/tests/integration/components/customer-panel-test.js b/tests/integration/components/customer-panel-test.js new file mode 100644 index 0000000..d9b55e7 --- /dev/null +++ b/tests/integration/components/customer-panel-test.js @@ -0,0 +1,26 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'dummy/tests/helpers'; +import { render } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; + +module('Integration | Component | customer-panel', function (hooks) { + setupRenderingTest(hooks); + + test('it renders', async function (assert) { + // Set any properties with this.set('myProperty', 'value'); + // Handle any actions with this.set('myAction', function(val) { ... }); + + await render(hbs``); + + assert.dom().hasText(''); + + // Template block usage: + await render(hbs` + + template block text + + `); + + assert.dom().hasText('template block text'); + }); +}); diff --git a/tests/integration/components/customer-panel/details-test.js b/tests/integration/components/customer-panel/details-test.js new file mode 100644 index 0000000..f5dcf9e --- /dev/null +++ b/tests/integration/components/customer-panel/details-test.js @@ -0,0 +1,26 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'dummy/tests/helpers'; +import { render } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; + +module('Integration | Component | customer-panel/details', function (hooks) { + setupRenderingTest(hooks); + + test('it renders', async function (assert) { + // Set any properties with this.set('myProperty', 'value'); + // Handle any actions with this.set('myAction', function(val) { ... }); + + await render(hbs``); + + assert.dom().hasText(''); + + // Template block usage: + await render(hbs` + + template block text + + `); + + assert.dom().hasText('template block text'); + }); +}); diff --git a/tests/integration/components/customer-panel/orders-test.js b/tests/integration/components/customer-panel/orders-test.js new file mode 100644 index 0000000..5a965e5 --- /dev/null +++ b/tests/integration/components/customer-panel/orders-test.js @@ -0,0 +1,26 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'dummy/tests/helpers'; +import { render } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; + +module('Integration | Component | customer-panel/orders', function (hooks) { + setupRenderingTest(hooks); + + test('it renders', async function (assert) { + // Set any properties with this.set('myProperty', 'value'); + // Handle any actions with this.set('myAction', function(val) { ... }); + + await render(hbs``); + + assert.dom().hasText(''); + + // Template block usage: + await render(hbs` + + template block text + + `); + + assert.dom().hasText('template block text'); + }); +}); diff --git a/tests/unit/controllers/customers/index/view-test.js b/tests/unit/controllers/customers/index/view-test.js new file mode 100644 index 0000000..3d9a2d0 --- /dev/null +++ b/tests/unit/controllers/customers/index/view-test.js @@ -0,0 +1,12 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'dummy/tests/helpers'; + +module('Unit | Controller | customers/index/view', function (hooks) { + setupTest(hooks); + + // TODO: Replace this with your real tests. + test('it exists', function (assert) { + let controller = this.owner.lookup('controller:customers/index/view'); + assert.ok(controller); + }); +}); diff --git a/tests/unit/routes/customers/index/view-test.js b/tests/unit/routes/customers/index/view-test.js new file mode 100644 index 0000000..8ae4a70 --- /dev/null +++ b/tests/unit/routes/customers/index/view-test.js @@ -0,0 +1,11 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'dummy/tests/helpers'; + +module('Unit | Route | customers/index/view', function (hooks) { + setupTest(hooks); + + test('it exists', function (assert) { + let route = this.owner.lookup('route:customers/index/view'); + assert.ok(route); + }); +}); From 5f28a54a8899a6fcb410d8335c049c26ee2474f5 Mon Sep 17 00:00:00 2001 From: Turtuvshin Date: Wed, 13 Mar 2024 15:00:35 +0800 Subject: [PATCH 02/32] Added customer detail panel --- .prettierrc.js | 8 +- addon/components/customer-panel.hbs | 2 +- addon/components/customer-panel.js | 5 +- addon/components/customer-panel/details.hbs | 14 ++-- package.json | 6 ++ translations/en-us.yaml | 87 ++++++++++++--------- 6 files changed, 71 insertions(+), 51 deletions(-) diff --git a/.prettierrc.js b/.prettierrc.js index bf64a90..dfd67dc 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -2,7 +2,7 @@ module.exports = { trailingComma: 'es5', - tabWidth: 4, + tabWidth: 2, semi: true, singleQuote: true, printWidth: 190, @@ -13,5 +13,11 @@ module.exports = { singleQuote: false, }, }, + { + files: '*.{yml,yaml}', + options: { + tabWidth: 2, + }, + }, ], }; diff --git a/addon/components/customer-panel.hbs b/addon/components/customer-panel.hbs index cdda5bb..a5ea5ff 100644 --- a/addon/components/customer-panel.hbs +++ b/addon/components/customer-panel.hbs @@ -18,7 +18,7 @@
+ + + + + + + + + + + + + {{#each this.orders as |order|}} + + + + + + + + + + {{/each}} + +
{{t "storefront.component.widget.orders.id-column"}}{{t "storefront.common.amount"}}{{t "storefront.common.customer"}}{{t "storefront.common.driver"}}{{t "storefront.common.created"}}{{t "storefront.common.status"}}
{{order.public_id}}{{format-currency order.meta.total order.meta.currency}}{{n-a order.customer_name}}{{n-a order.driver_name}}{{order.createdAgo}} + + + {{#if order.isFresh}} +
+ +
+ {{#each this.orders as |order|}} +
+
+
+ {{order.public_id}} +
{{order.createdAt}}
+
{{order.createdAgo}}
+
+
+ +
{{format-currency order.meta.total order.meta.currency}}
+
+
+
+
+ +
+
{{t "storefront.component.widget.orders.customer"}}: {{n-a order.customer_name}}
+
{{t "storefront.component.widget.orders.driver"}}: {{n-a order.driver_name}}
+
+
+
+ {{t "storefront.component.widget.orders.subtotal"}} + {{format-currency order.meta.subtotal order.meta.currency}} +
+ {{#unless order.meta.is_pickup}} +
+ {{t "storefront.component.widget.orders.delivery-fee"}} + {{format-currency order.meta.delivery_fee order.meta.currency}} +
+ {{/unless}} + {{#if order.meta.tip}} +
+ {{t "storefront.component.widget.order.tip"}} + {{get-tip-amount order.meta.tip order.meta.subtotal order.meta.currency}} +
+ {{/if}} + {{#if order.meta.delivery_tip}} +
+ {{t "storefront.component.widget.order.delivery-tip"}} + {{get-tip-amount order.meta.delivery_tip order.meta.subtotal order.meta.currency}} +
+ {{/if}} +
+ {{t "storefront.component.widget.order.tip"}} + {{format-currency order.meta.total order.meta.currency}} +
+
+
+
+ {{/each}} +
+ + \ No newline at end of file diff --git a/addon/components/customer-panel/orders.js b/addon/components/customer-panel/orders.js index 9086618..0c4b291 100644 --- a/addon/components/customer-panel/orders.js +++ b/addon/components/customer-panel/orders.js @@ -1,3 +1,74 @@ import Component from '@glimmer/component'; +import { tracked } from '@glimmer/tracking'; +import { inject as service } from '@ember/service'; +import { inject as controller } from '@ember/controller'; +import { action, computed, get } from '@ember/object'; +import { later } from '@ember/runloop'; +import { task } from 'ember-concurrency-decorators'; +export default class CustomerPanelOrdersComponent extends Component { + @service store; + @service storefront; + @service fetch; + @service intl; + @service appCache; + @service modalsManager; + @tracked isLoading = true; + @tracked orders = []; + @controller('orders.index.view') orderDetailsController; -export default class CustomerPanelOrdersComponent extends Component {} + @computed('args.title') get title() { + return this.args.title ?? this.intl.t('storefront.component.widget.orders.widget-title'); + } + + constructor() { + super(...arguments); + this.reloadOrders.perform(); + } + + @task *reloadOrders(params = {}) { + console.log('Reloading orders...'); + this.orders = yield this.fetchOrders(params); + } + + @action fetchOrders(params = {}) { + this.isLoading = true; + + return new Promise((resolve) => { + this.fetch + .get('customers/orders', null, { + namespace: 'storefront/int/v1', + normalizeToEmberData: true, + }) + .then((orders) => { + this.isLoading = false; + console.log('Orders: ', orders); + resolve(orders); + }) + .catch((err) => { + this.isLoading = false; + console.error('Error fetching orders: ', err); + resolve(this.orders); + }); + }); + } + + @action async viewOrder(order) { + await this.orderDetailsController.viewOrder(order); + } + + @action async acceptOrder(order) { + await this.orderDetailsController.acceptOrder(order); + } + + @action markAsReady(order) { + this.orderDetailsController.markAsReady(order); + } + + @action markAsCompleted(order) { + this.orderDetailsController.markAsCompleted(order); + } + + @action async assignDriver(order) { + await this.orderDetailsController.assignDriver(order); + } +} From 654d9329775c214f794a8a9e2331de200fbd2f76 Mon Sep 17 00:00:00 2001 From: Turtuvshin Date: Mon, 18 Mar 2024 16:01:37 +0800 Subject: [PATCH 05/32] Add customer order list and display customer details --- addon/components/customer-panel.hbs | 20 +- addon/components/customer-panel/details.hbs | 52 ++--- addon/components/customer-panel/orders.hbs | 246 ++++++++++---------- addon/components/customer-panel/orders.js | 26 ++- 4 files changed, 177 insertions(+), 167 deletions(-) diff --git a/addon/components/customer-panel.hbs b/addon/components/customer-panel.hbs index a5ea5ff..2e831c9 100644 --- a/addon/components/customer-panel.hbs +++ b/addon/components/customer-panel.hbs @@ -11,14 +11,12 @@ >
-
-
+
@@ -28,29 +26,29 @@
{{this.contact.name}} - +
-

{{this.contact.name}}

+

{{this.customer.name}}

- {{smart-humanize this.contact.type}} + {{smart-humanize this.customer.type}}
- +
diff --git a/addon/components/customer-panel/details.hbs b/addon/components/customer-panel/details.hbs index 43df2df..50a3422 100644 --- a/addon/components/customer-panel/details.hbs +++ b/addon/components/customer-panel/details.hbs @@ -1,36 +1,34 @@
- -
+
-
-
{{t "storefront.customers.customer-panel.details.web-url"}}
-
{{n-a @customer.name}}
-
+
+
{{t "storefront.customers.customer-panel.details.web-url"}}
+
{{n-a @customer.name}}
+
-
-
{{t "storefront.common.title"}}
-
{{n-a @customer.title}}
-
+
+
{{t "storefront.common.title"}}
+
{{n-a @customer.title}}
+
-
-
{{t "storefront.common.internal-id"}}
-
{{n-a @customer.internal_id}}
-
+
+
{{t "storefront.common.internal-id"}}
+
{{n-a @customer.internal_id}}
+
-
-
{{t "storefront.common.email"}}
-
{{n-a @customer.email}}
-
+
+
{{t "storefront.common.email"}}
+
{{n-a @customer.email}}
+
-
-
{{t "storefront.common.phone"}}
-
{{n-a @customer.phone}}
-
+
+
{{t "storefront.common.phone"}}
+
{{n-a @customer.phone}}
+
-
-
{{t "storefront.common.type"}}
-
-
+
+
{{t "storefront.common.type"}}
+
- +
\ No newline at end of file diff --git a/addon/components/customer-panel/orders.hbs b/addon/components/customer-panel/orders.hbs index 561bbeb..4ee0b73 100644 --- a/addon/components/customer-panel/orders.hbs +++ b/addon/components/customer-panel/orders.hbs @@ -1,140 +1,136 @@
- - {{#if this.isLoading}} -
- -
- {{/if}} -