Skip to content

Commit

Permalink
Merge pull request #27 from fleetbase/dev-v0.3.10
Browse files Browse the repository at this point in the history
v0.3.10
  • Loading branch information
roncodes authored Jul 2, 2024
2 parents 74b2b4a + 30b3568 commit 9c1ebc7
Show file tree
Hide file tree
Showing 21 changed files with 447 additions and 47 deletions.
3 changes: 3 additions & 0 deletions addon/components/add-product-as-entity-button.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{{#if (eq @order.type "storefront")}}
<Button @type="magic" @icon="square-plus" @text="Add Product from Storefront" @onClick={{this.promptProductSelection}} />
{{/if}}
58 changes: 58 additions & 0 deletions addon/components/add-product-as-entity-button.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import { isArray } from '@ember/array';
import { inject as service } from '@ember/service';

export default class AddProductAsEntityButtonComponent extends Component {
@service modalsManager;
@service fetch;
@service notifications;
@tracked order;
@tracked controller;

constructor(owner, { order, controller }) {
super(...arguments);
this.order = order;
this.controller = controller;
}

@action promptProductSelection() {
this.modalsManager.show('modals/select-product', {
title: 'Select Product to Add to Order',
modalClass: 'modal-lg',
acceptButtonText: 'Add Products',
acceptButtonDisabled: true,
selectedProducts: [],
selectedStorefront: null,
confirm: (modal) => {
modal.startLoading();

const selectedStorefront = modal.getOption('selectedStorefront');
const selectedProducts = modal.getOption('selectedProducts', []);
const products = selectedProducts.map((product) => product.id);

return this.fetch
.post('products/create-entities', { products }, { namespace: 'storefront/int/v1', normalizeToEmberData: true, normalizeModelType: 'entity' })
.then((entities) => {
this.controller.addEntities(entities);
if (isArray(this.order.meta)) {
this.order.meta.pushObjects([
{
key: 'storefront',
value: selectedStorefront.name
},
{
key: 'storefront_id',
value: selectedStorefront.public_id
}
]);
}
})
.catch((error) => {
this.notifications.serverError(error);
});
},
});
}
}
1 change: 1 addition & 0 deletions addon/components/customer-panel.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import CustomerPanelDetailsComponent from './customer-panel/details';
import CustomerPanelOrdersComponent from './customer-panel/orders';
import contextComponentCallback from '@fleetbase/ember-core/utils/context-component-callback';
import applyContextComponentArguments from '@fleetbase/ember-core/utils/apply-context-component-arguments';

export default class CustomerPanelComponent extends Component {
/**
* Service for fetching data.
Expand Down
87 changes: 87 additions & 0 deletions addon/components/modals/select-product.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<Modal::Default @modalIsOpened={{@modalIsOpened}} @options={{@options}} @confirm={{@onConfirm}} @decline={{@onDecline}}>
<div class="modal-body-container">
<div class="grid grid-cols-3 gap-2">
<InputGroup @name="Select Store">
<div class="fleetbase-model-select fleetbase-power-select ember-model-select">
<PowerSelect
@options={{this.stores}}
@selected={{this.selectedStorefront}}
@onChange={{this.onStorefrontSelect}}
@registerAPI={{this.setStorefrontSelectApi}}
@optionValue="name"
@placeholder="Select Storefront"
@triggerClass="form-select form-input"
@disabled={{not this.stores}}
as |storefront|
>
{{storefront.name}}
</PowerSelect>
</div>
</InputGroup>

{{#if (and this.selectedStorefront this.productCategories)}}
<InputGroup @name="Select Product Category">
<div class="fleetbase-model-select fleetbase-power-select ember-model-select">
<PowerSelect
@options={{this.productCategories}}
@selected={{this.selectedCategory}}
@onChange={{this.onSelectProductCategory}}
@disabled={{not this.productCategories}}
@placeholder="Filter by Product Category"
@triggerClass="form-select form-input"
as |category|
>
{{category.name}}
</PowerSelect>
</div>
</InputGroup>
{{/if}}

{{#if this.selectedProducts}}
<div class="py-2 flex items-center dark:text-white text-gray-900">
Selected
{{pluralize this.selectedProducts.length "Product"}}
</div>
{{/if}}
</div>
<div class="min-h-4r">
{{#if this.selectedStorefront}}
{{#if this.fetchProductsForStorefront.isRunning}}
<Spinner @loadingMessage="Loading products..." />
{{else}}
<div class="grid grid-cols-3 lg:grid-cols-4 gap-2">
{{#each this.products as |product|}}
{{#let (in-array product this.selectedProducts) as |isSelected|}}
<div
class="border bg-white dark:bg-gray-900 dark:text-gray-100 text-center rounded-md px-2 py-3
{{if isSelected 'border-blue-500 dark:border-blue-500 outline-offset-2 outline-blue-400 outline-dashed' 'border-gray-200 dark:border-gray-700'}}"
>
<div class="flex flex-col items-center justify-center overflow-hidden">
<div class="mb-3 flex items-center justify-center">
<img src={{product.primary_image_url}} alt={{product.name}} class="w-24 h-24" />
</div>
<h4 class="font-semibold mb-1">{{product.name}}</h4>
<p class="text-sm truncate">{{product.description}}</p>
<p class="mb-2 text-sm text-green-400">{{format-currency product.price product.currency}}</p>
<div class="flex items-center justify-evenly space-x-4">
<Button
@type={{if isSelected "danger" "default"}}
@icon="circle-plus"
@text={{if isSelected "Remove Product" "Add Product"}}
@onClick={{fn this.toggleProductSelection product}}
/>
</div>
</div>
</div>
{{/let}}
{{else}}
<div>
<h3 class="dark:text-gray-100 text-opacity-75 text-sm">No products</h3>
</div>
{{/each}}
</div>
{{/if}}
{{/if}}
</div>
</div>
</Modal::Default>
88 changes: 88 additions & 0 deletions addon/components/modals/select-product.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import { inject as service } from '@ember/service';
import { task } from 'ember-concurrency';
import { pluralize } from 'ember-inflector';

export default class ModalsSelectProductComponent extends Component {
@service fetch;
@service notifications;
@service modalsManager;
@tracked stores = [];
@tracked productCategories = [];
@tracked products = [];
@tracked selectedProducts = [];
@tracked selectedStorefront;
@tracked selectedCategory;
@tracked storefrontSelectAPI;

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

@action toggleProductSelection(product) {
if (this.selectedProducts.includes(product)) {
this.selectedProducts.removeObject(product);
} else {
this.selectedProducts.pushObject(product);
}

this.updateSelectedProducts();
}

updateSelectedProducts() {
this.modalsManager.setOption('selectedProducts', this.selectedProducts);
this.modalsManager.setOption('acceptButtonDisabled', this.selectedProducts.length === 0);
this.modalsManager.setOption('acceptButtonText', this.selectedProducts.length ? `Add ${pluralize(this.selectedProducts.length, 'Product')}` : 'Add Products');
this.modalsManager.setOption('selectedStorefront', this.selectedStorefront);
}

@action setStorefrontSelectApi(storefrontSelectAPI) {
this.storefrontSelectAPI = storefrontSelectAPI;
}

@action onStorefrontSelect(storefront) {
this.selectedStorefront = storefront;
this.selectedCategory = null;
this.selectedProducts = [];
this.updateSelectedProducts();
this.fetchCategoriesForStorefront.perform(storefront);
this.fetchProductsForStorefront.perform(storefront);
}

@action onSelectProductCategory(category) {
this.selectedCategory = category;
this.fetchProductsForStorefront.perform(this.selectedStorefront, { category_slug: category.slug });
}

@task *fetchStorefronts(queryParams = {}) {
try {
this.stores = yield this.fetch.get('stores', queryParams, { namespace: 'storefront/int/v1', normalizeToEmberData: true });
} catch (error) {
this.notifications.serverError(error);
return;
}

if (this.stores && this.storefrontSelectAPI) {
this.storefrontSelectAPI.actions.select(this.stores[0]);
}
}

@task *fetchProductsForStorefront(storefront, queryParams = {}) {
try {
this.products = yield this.fetch.get('products', { store_uuid: storefront.id, ...queryParams }, { namespace: 'storefront/int/v1', normalizeToEmberData: true });
} catch (error) {
this.notifications.serverError(error);
}
}

@task *fetchCategoriesForStorefront(storefront, queryParams = {}) {
try {
this.productCategories = yield this.fetch.get('categories', { for: 'storefront_product', owner_uuid: storefront.id, limit: -1, ...queryParams }, { normalizeToEmberData: true });
} catch (error) {
this.notifications.serverError(error);
}
}
}
4 changes: 1 addition & 3 deletions addon/components/order-panel.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import Component from '@glimmer/component';

import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import applyContextComponentArguments from '@fleetbase/ember-core/utils/apply-context-component-arguments';
import contextComponentCallback from '@fleetbase/ember-core/utils/context-component-callback';
import { tracked } from '@glimmer/tracking';

export default class OrderPanelComponent extends Component {
@tracked context = null;

constructor() {
super(...arguments);

applyContextComponentArguments(this);
}

Expand Down
4 changes: 2 additions & 2 deletions addon/components/widget/orders.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@
<tr class="h-12">
<td><a href="javascript:;" {{on "click" (fn this.viewOrder order)}}>{{order.public_id}}</a></td>
<td>{{format-currency order.meta.total order.meta.currency}}</td>
<td>{{n-a order.customer_name}}</td>
<td>{{n-a order.driver_name}}</td>
<td>{{n-a order.customer.name}}</td>
<td>{{n-a order.driver_assigned.name}}</td>
<td>{{order.createdAgo}}</td>
<td>
<Badge @status={{order.status}} />
Expand Down
1 change: 1 addition & 0 deletions addon/components/widget/orders.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ 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';

export default class WidgetOrdersComponent extends Component {
@service store;
@service storefront;
Expand Down
5 changes: 4 additions & 1 deletion addon/controllers/products/index/category/new.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,10 @@ export default class ProductsIndexCategoryNewController extends BaseController {

this.loader.removeLoader(loader);
this.notifications.success(this.intl.t('storefront.products.index.new.new-product-created-success'));
yield this.transitionToRoute('products.index.category', category.slug);

try {
yield this.transitionToRoute('products.index.category', category.slug);
} catch (error) {}
this.reset();
}

Expand Down
2 changes: 2 additions & 0 deletions addon/engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import config from './config/environment';
import services from '@fleetbase/ember-core/exports/services';
import StorefrontKeyMetricsWidget from './components/widget/storefront-key-metrics';
import StorefrontOrderSummaryComponent from './components/storefront-order-summary';
import AddProductAsEntityButtonComponent from './components/add-product-as-entity-button';

const { modulePrefix } = config;
const externalRoutes = ['console', 'extensions'];
Expand Down Expand Up @@ -35,6 +36,7 @@ export default class StorefrontEngine extends Engine {

// register component to views
universe.registerRenderableComponent('@fleetbase/fleetops-engine', 'fleet-ops:template:operations:orders:view', StorefrontOrderSummaryComponent);
universe.registerRenderableComponent('@fleetbase/fleetops-engine', 'fleet-ops:template:operations:orders:new:entities-input', AddProductAsEntityButtonComponent);

// register widgets
universe.registerDefaultDashboardWidgets([KeyMetricsWidgetDefinition]);
Expand Down
1 change: 1 addition & 0 deletions app/components/add-product-as-entity-button.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from '@fleetbase/storefront-engine/components/add-product-as-entity-button';
1 change: 1 addition & 0 deletions app/components/modals/select-product.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from '@fleetbase/storefront-engine/components/modals/select-product';
6 changes: 3 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "fleetbase/storefront-api",
"version": "0.3.9",
"version": "0.3.10",
"description": "Headless Commerce & Marketplace Extension for Fleetbase",
"keywords": [
"fleetbase-extension",
Expand All @@ -22,8 +22,8 @@
],
"require": {
"php": "^8.0",
"fleetbase/core-api": "^1.4.25",
"fleetbase/fleetops-api": "^0.4.30",
"fleetbase/core-api": "^1.4.26",
"fleetbase/fleetops-api": "^0.5.0",
"geocoder-php/google-maps-places-provider": "^1.4",
"laravel-notification-channels/apn": "^5.0",
"laravel-notification-channels/fcm": "^4.1",
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@fleetbase/storefront-engine",
"version": "0.3.9",
"version": "0.3.10",
"description": "Headless Commerce & Marketplace Extension for Fleetbase",
"fleetbase": {
"route": "storefront",
Expand Down Expand Up @@ -44,7 +44,7 @@
},
"dependencies": {
"@fleetbase/ember-core": "^0.2.11",
"@fleetbase/ember-ui": "^0.2.16",
"@fleetbase/ember-ui": "^0.2.17",
"@fleetbase/fleetops-data": "^0.1.15",
"@babel/core": "^7.23.2",
"@fortawesome/ember-fontawesome": "^0.4.1",
Expand Down
Loading

0 comments on commit 9c1ebc7

Please sign in to comment.