Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated UI for order details page and implemented feature to select store pickup location (#85zru5e18) #25

Merged
merged 7 commits into from
Mar 30, 2023
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ VUE_APP_CACHE_MAX_AGE=VUE_APP_CACHE_MAX_AGE
VUE_APP_SHOPIFY_API_KEY=VUE_APP_SHOPIFY_API_KEY
VUE_APP_SHOPIFY_REDIRECT_URI=VUE_APP_SHOPIFY_REDIRECT_URI
VUE_APP_SHOPIFY_SCOPES=VUE_APP_SHOPIFY_SCOPES
VUE_APP_SHOPIFY_SHOP_CONFIG=VUE_APP_SHOPIFY_SHOP_CONFIG
VUE_APP_SHOPIFY_SHOP_CONFIG=VUE_APP_SHOPIFY_SHOP_CONFIG
VUE_APP_DEFAULT_STORETYPE=["storeType: WAREHOUSE OR storeType: RETAIL_STORE"]
14 changes: 14 additions & 0 deletions src/services/FacilityService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import api from '@/api'

const getStores = async (payload: any): Promise <any> => {
// TODO implement caching
return api({
url: "storeLookup",
method: "post",
data: payload,
});
}

export const FacilityService = {
getStores
}
13 changes: 13 additions & 0 deletions src/services/UtilityService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import api from '@/api';

const getGeoLocation = async (payload: any): Promise <any> => {
return api({
url: "postcodeLookup",
method: "post",
data: payload,
});
}

export const UtilityService = {
getGeoLocation
}
13 changes: 10 additions & 3 deletions src/store/modules/order/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import * as types from './mutation-types'
import { OrderService } from '@/services/OrderService'
import { hasError, showToast } from '@/utils'
import { translate } from '@/i18n'
import { DateTime } from 'luxon'

const actions: ActionTree<OrderState, RootState> = {
async getDraftOrder ({ commit, dispatch }, orderId) {
Expand All @@ -18,8 +17,16 @@ const actions: ActionTree<OrderState, RootState> = {
if (resp.status === 200 && !hasError(resp) && resp.data.response?.draft_order) {
const order = resp.data.response.draft_order;
const productSkus = order.line_items.map((item: any) => item.sku).filter((sku: any) => sku);
this.dispatch('stock/checkInventoryByFacility', productSkus);
this.dispatch('stock/checkPreorderItemAvailability', productSkus);
order.line_items.map((item: any) => {
const isBopis = item.properties.some((property: any) => property.name === "_pickupstore");
if (isBopis) {
item.deliveryMethodTypeId = 'STOREPICKUP'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should either decide upon the property or remove the deliveryMethodTypeId method upon saving the order. Adding a property could give uncertain behaviour

item.selectedFacility = item.properties.find((property: any) => property.name == 'Store Pickup').value
} else {
item.deliveryMethodTypeId = 'STANDARD'
}
})
dispatch('stock/checkPreorderItemAvailability', productSkus, { root: true });
commit(types.DRAFT_ORDER_UPDATED, order);
} else {
console.error(resp);
Expand Down
151 changes: 119 additions & 32 deletions src/views/OrderDetail.vue
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,17 @@
<p>{{ item.variant_title }}</p>
<p class="ion-text-wrap">{{ $t("SKU") }}: {{ item.sku }}</p>
</ion-label>
<ion-button :disabled="JSON.stringify(order) == JSON.stringify(initialOrder)" fill="clear" color="medium" slot="end" @click="undo(item)">
<ion-icon slot="icon-only" :icon="arrowUndoOutline" />
</ion-button>
</ion-item>
<ion-item>
<ion-checkbox :checked="isBopisItem(item)" slot="start" @ionChange="markBopisItem(item, $event)" />
<ion-label>{{ $t("Pickup") }}</ion-label>
<ion-note slot="end">{{ getProductStock(item.sku, shopifyStores[0]?.storeCode) }} {{ $t("in stock") }}</ion-note>
<ion-label>{{ $t('Delivery method') }}</ion-label>
<ion-select interface="popover" :value="item.deliveryMethodTypeId" @ionChange="updateDeliveryMethod($event, item)">
<ion-select-option v-for="method in deliveryMethods" :key="method.value" :value="method.value">{{ method.name }}</ion-select-option>
</ion-select>
</ion-item>
<ion-radio-group :value="checkPreorderBackorderItem(item)" @ionChange="markPreorderBackorderItem(item, $event)">
<ion-radio-group v-if="item.deliveryMethodTypeId !== 'STOREPICKUP'" :value="checkPreorderBackorderItem(item)" @ionChange="markPreorderBackorderItem(item, $event)">
<ion-item class="border-top">
<ion-radio :disabled="isPreorderOrBackorderProduct(item, 'PRE-ORDER')" slot="start" value="Pre Order" />
<ion-label>{{ $t("Pre Order") }}</ion-label>
Expand All @@ -47,68 +51,89 @@
<ion-note slot="end" :color="getEstimatedDeliveryDate(item, 'BACKORDER') ? '' : 'warning'">{{ getEstimatedDeliveryDate(item, "BACKORDER") ? getEstimatedDeliveryDate(item, "BACKORDER") : $t("No shipping estimates") }}</ion-note>
</ion-item>
</ion-radio-group>
<ion-button v-else-if="item.deliveryMethodTypeId === 'STOREPICKUP' && !item.selectedFacility" @click="updatePickupLocation(item)" expand="block" fill="outline">{{ $t("Select pickup location")}}</ion-button>
<ion-item v-else>
<ion-label class="ion-text-wrap">{{ item.selectedFacility }}</ion-label>
<ion-button slot="end" @click="updatePickupLocation(item)" color="medium" fill="outline">{{ $t("Change Store")}}</ion-button>
</ion-item>
</ion-card>
</main>
<div class="text-center center-align">
<ion-button @click="updateDraftOrder()">{{ $t("Save changes to order") }}</ion-button>
<ion-button :disabled="!isChanged()" @click="save()">{{ $t("Save changes to order") }}</ion-button>
</div>
</div>
</ion-content>
</ion-page>
</template>
<script lang="ts">
import {
alertController,
IonButton,
IonCard,
IonCheckbox,
IonContent,
IonHeader,
IonItem,
IonLabel,
IonList,
IonNote,
IonPage,
IonSelect,
IonSelectOption,
IonTitle,
IonToolbar,
IonRadio,
IonRadioGroup
IonRadioGroup,
modalController
} from "@ionic/vue";
import {
sendOutline,
swapVerticalOutline,
callOutline,
mailOutline,
} from "ionicons/icons";
import { arrowUndoOutline } from "ionicons/icons";
import { defineComponent } from 'vue';
import { mapGetters, useStore } from 'vuex';
import { useRouter } from 'vue-router';
import { DateTime } from 'luxon';
import { Redirect } from "@shopify/app-bridge/actions";
import createApp from "@shopify/app-bridge";
import PickupLocationModal from "./PickupLocationModal.vue";
import { translate } from "@/i18n";
import { showToast } from "@/utils";

export default defineComponent({
name: 'Home',
name: 'OrderDetail',
components: {
IonButton,
IonCard,
IonCheckbox,
IonContent,
IonHeader,
IonItem,
IonLabel,
IonList,
IonNote,
IonPage,
IonSelect,
IonSelectOption,
IonTitle,
IonToolbar,
IonRadio,
IonRadioGroup
},
data() {
return {
initialOrder: {} as any,
deliveryMethods: [
{
name: 'Store pickup',
value: 'STOREPICKUP'
},
{
name: 'Shipping',
value: 'STANDARD'
}
]
}
},
computed: {
...mapGetters({
order: 'order/getDraftOrder',
shopifyStores: 'shop/getStores',
getProductStock: 'stock/getProductStock',
getPreorderItemAvailability: 'stock/getPreorderItemAvailability',
routeParams: 'shop/getRouteParams'
})
Expand All @@ -118,24 +143,16 @@ export default defineComponent({
if (this.$route.query.id) {
await this.store.dispatch('order/getDraftOrder', this.$route.query.id);
}
this.initialOrder = JSON.parse(JSON.stringify(this.order));
},
methods: {
isBopisItem(item: any){
return item.properties.some((property: any) => property.name === "Pickup Store");
},
isPreorderOrBackorderProduct(item: any, label: string){
adityasharma7 marked this conversation as resolved.
Show resolved Hide resolved
const product = this.getPreorderItemAvailability(item.sku);
return !(product.label === label);
},
markBopisItem (item: any, event: any) {
if(this.isBopisItem(item)){
// Need to remove the 'Pickup Store' check, currently kept it for backward compatibility.
item.properties = item.properties.filter((property: any) => !(property.name === '_pickupstore' || property.name === 'Store Pickup' || property.name === 'Pickup Store'))
} else {
const store = this.shopifyStores[0];
const address = [store.storeName, store.address1, store.city].filter((value: any) => value).join(", ");
item.properties.push({ name: '_pickupstore', value: store.storeCode }, { name: 'Store Pickup', value: address })
}
updateDeliveryMethod(event: any, item: any) {
item.deliveryMethodTypeId = event.detail.value;
if (item.deliveryMethodTypeId !== 'STOREPICKUP') item.properties = item.properties.filter((property: any) => !(property.name === '_pickupstore' || property.name === 'Store Pickup' || property.name === 'Pickup Store'))
this.store.dispatch('order/updateLineItems', this.order)
},
markPreorderBackorderItem (item: any, event: any) {
Expand All @@ -148,6 +165,7 @@ export default defineComponent({
},
async updateDraftOrder () {
const shopConfig = JSON.parse(process.env.VUE_APP_SHOPIFY_SHOP_CONFIG);
this.order.line_items.map((lineItem: any) => delete lineItem.deliveryMethodTypeId)
await this.store.dispatch('order/updateDraftOrder', this.order).then(() => {
const app = createApp({
apiKey: shopConfig[this.routeParams.shop].apiKey,
Expand Down Expand Up @@ -178,17 +196,86 @@ export default defineComponent({
if(product.label === label){
return DateTime.fromISO(product.estimatedDeliveryDate).toFormat("MM/dd/yyyy");
}
},
async updatePickupLocation(item: any) {
const modal = await modalController
.create({
component: PickupLocationModal,
// Adding backdropDismiss as false because on dismissing the modal through backdrop,
// backrop.role returns 'backdrop' giving unexpected result
backdropDismiss: false,
componentProps: { item, facilityId: item.properties.find((property: any) => property.name == '_pickupstore')?.value }
})
modal.onDidDismiss().then((result) => {
if (result.role) {
// role will have the passed data
const facilityData = result.role as any
item.selectedFacility = facilityData.selectedFacility
item.properties.push({ name: '_pickupstore', value: facilityData.storeCode }, { name: 'Store Pickup', value: item.selectedFacility })
this.store.dispatch('order/updateLineItems', this.order);
}
});
return modal.present();
},
async save() {
const message = this.$t("Are you sure you want to save the changes?");
const alert = await alertController.create({
header: this.$t("Save changes"),
message,
buttons: [
{
text: this.$t("Cancel"),
},
{
text: this.$t("Confirm"),
handler: () => {
this.updateDraftOrder()
}
}
],
});
return alert.present();
},
isChanged() {
if (Object.keys(this.order).length && Object.keys(this.initialOrder).length) {
return this.order.line_items.some((updatedItem: any, index: number) => {
const isMethodUpdated = updatedItem.deliveryMethodTypeId !== this.initialOrder.line_items[index].deliveryMethodTypeId
return updatedItem.deliveryMethodTypeId !== 'STOREPICKUP' ? isMethodUpdated : isMethodUpdated && updatedItem.selectedFacility;
})
}
},
async undo(item: any) {
const header = this.$t('Clear edits')
const message = this.$t('Are you sure you want to undo the changes you’ve made to this order item?')

const alert = await alertController
.create({
header: header,
message: message,
buttons: [{
text: this.$t('Cancel'),
role: 'cancel'
},{
text: this.$t('Clear'),
handler: async () => {
// finding the initial line_item (without edits) and updating it in this.order
const initialItem = this.initialOrder.line_items.find((lineItem: any) => lineItem.id === item.id)
const index = this.order.line_items.findIndex((lineItem: any) => lineItem.id === item.id)
this.order.line_items.splice(index, 1, initialItem)
this.store.dispatch('order/updateLineItems', this.order)
showToast(translate('Previous edits cleared successfully'))
}
}]
});
return alert.present();
}
},
setup() {
const store = useStore();
const router = useRouter();

return {
sendOutline,
swapVerticalOutline,
callOutline,
mailOutline,
arrowUndoOutline,
router,
store
};
Expand Down
Loading