generated from hotwax/dxp-components
-
Notifications
You must be signed in to change notification settings - Fork 27
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
Implemented: Unified inventory upload(#304)
- Loading branch information
Showing
43 changed files
with
2,458 additions
and
494 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,207 @@ | ||
<template> | ||
<ion-header> | ||
<ion-toolbar> | ||
<ion-buttons slot="start"> | ||
<ion-button @click="closeModal"> | ||
<ion-icon slot="icon-only" :icon="closeOutline" /> | ||
</ion-button> | ||
</ion-buttons> | ||
<ion-title>{{ translate("Add product") }}</ion-title> | ||
</ion-toolbar> | ||
</ion-header> | ||
<ion-content ref="contentRef" :scroll-events="true" @ionScroll="enableScrolling()"> | ||
<ion-searchbar v-model="queryString" :placeholder="translate('Search SKU or product name')" @keyup.enter="handleSearch" @ionInput="handleInput"/> | ||
|
||
<template v-if="products.length"> | ||
<ion-list v-for="product in products" :key="product.productId"> | ||
<ion-item lines="none"> | ||
<ion-thumbnail slot="start"> | ||
<Image :src="product.mainImageUrl" /> | ||
</ion-thumbnail> | ||
<ion-label> | ||
<h2>{{ getProductIdentificationValue(productIdentificationPref.primaryId, getProductById(product.productId)) ? getProductIdentificationValue(productIdentificationPref.primaryId, getProductById(product.productId)) : getProductById(product.productId).productName }}</h2> | ||
<p>{{ getProductIdentificationValue(productIdentificationPref.secondaryId, getProductById(product.productId)) }}</p> | ||
</ion-label> | ||
<ion-icon v-if="isProductAvailableInShipment(product.productId)" color="success" :icon="checkmarkCircle" /> | ||
<ion-button v-else fill="outline" @click="addToShipment(product.productId)">{{ translate("Add to shipment") }}</ion-button> | ||
</ion-item> | ||
</ion-list> | ||
|
||
<ion-infinite-scroll @ionInfinite="loadMoreProducts($event)" threshold="100px" v-show="isScrollable" ref="infiniteScrollRef"> | ||
<ion-infinite-scroll-content loading-spinner="crescent" :loading-text="translate('Loading')" /> | ||
</ion-infinite-scroll> | ||
</template> | ||
|
||
<div v-else-if="queryString && isSearching && !products.length" class="empty-state"> | ||
<p>{{ translate("No product found") }}</p> | ||
</div> | ||
<div v-else class="empty-state"> | ||
<img src="../assets/images/empty-state-add-product-modal.png" alt="empty-state" /> | ||
<p>{{ translate("Enter a SKU, or product name to search a product") }}</p> | ||
</div> | ||
</ion-content> | ||
</template> | ||
|
||
<script> | ||
import { | ||
IonButton, | ||
IonButtons, | ||
IonContent, | ||
IonHeader, | ||
IonIcon, | ||
IonInfiniteScroll, | ||
IonItem, | ||
IonLabel, | ||
IonList, | ||
IonSearchbar, | ||
IonThumbnail, | ||
IonTitle, | ||
IonToolbar, | ||
modalController, | ||
} from "@ionic/vue"; | ||
import { closeOutline, checkmarkCircle } from "ionicons/icons" | ||
import { defineComponent, computed } from "vue" | ||
import { mapGetters, useStore } from "vuex"; | ||
import { useRouter } from "vue-router" | ||
import store from "@/store" | ||
import { translate, getProductIdentificationValue, useProductIdentificationStore } from "@hotwax/dxp-components"; | ||
import Image from "@/components/Image.vue" | ||
import { UtilService } from "@/services/UtilService"; | ||
import { hasError } from "@/adapter"; | ||
import { showToast } from '@/utils'; | ||
import logger from "@/logger"; | ||
export default defineComponent({ | ||
name: "AddProductModal", | ||
components: { | ||
IonButton, | ||
IonButtons, | ||
IonContent, | ||
IonHeader, | ||
IonIcon, | ||
IonInfiniteScroll, | ||
IonItem, | ||
IonLabel, | ||
IonList, | ||
IonSearchbar, | ||
IonThumbnail, | ||
IonTitle, | ||
IonToolbar, | ||
Image, | ||
}, | ||
data() { | ||
return { | ||
queryString: '', | ||
isSearching: false, | ||
isScrollingEnabled: false | ||
} | ||
}, | ||
async ionViewWillEnter() { | ||
this.isScrollingEnabled = false; | ||
}, | ||
unmounted() { | ||
store.dispatch("product/clearProducts"); | ||
}, | ||
props: ["shipmentId"], | ||
computed: { | ||
...mapGetters({ | ||
products: 'product/getProducts', | ||
isScrollable: 'product/isScrollable', | ||
getProductById: 'product/getProductById', | ||
isProductAvailableInShipment: 'product/isProductAvailableInShipment', | ||
}) | ||
}, | ||
methods: { | ||
enableScrolling() { | ||
const parentElement = this.$refs.contentRef.$el | ||
const scrollEl = parentElement.shadowRoot.querySelector("main[part='scroll']") | ||
let scrollHeight = scrollEl.scrollHeight, infiniteHeight = this.$refs.infiniteScrollRef.$el.offsetHeight, scrollTop = scrollEl.scrollTop, threshold = 100, height = scrollEl.offsetHeight | ||
const distanceFromInfinite = scrollHeight - infiniteHeight - scrollTop - threshold - height | ||
if(distanceFromInfinite < 0) { | ||
this.isScrollingEnabled = false; | ||
} else { | ||
this.isScrollingEnabled = true; | ||
} | ||
}, | ||
async handleSearch() { | ||
if(!this.queryString) { | ||
this.isSearching = false; | ||
store.dispatch("product/clearProducts"); | ||
return; | ||
} | ||
await this.getProducts(); | ||
this.isSearching = true; | ||
}, | ||
async getProducts( vSize, vIndex) { | ||
const viewSize = vSize ? vSize : process.env.VUE_APP_VIEW_SIZE; | ||
const viewIndex = vIndex ? vIndex : 0; | ||
const payload = { | ||
viewSize, | ||
viewIndex, | ||
queryString: this.queryString | ||
} | ||
if(this.queryString) { | ||
await this.store.dispatch("product/findProduct", payload); | ||
} | ||
}, | ||
async loadMoreProducts(event) { | ||
if(!(this.isScrollingEnabled && this.isScrollable)) { | ||
await event.target.complete(); | ||
} | ||
this.getProducts( | ||
undefined, | ||
Math.ceil(this.products.length / process.env.VUE_APP_VIEW_SIZE).toString() | ||
).then(() => { | ||
event.target.complete(); | ||
}) | ||
}, | ||
async addToShipment(productId) { | ||
let resp; | ||
const payload = { | ||
productId: productId, | ||
shipmentId: this.shipmentId, | ||
quantity: 1 | ||
} | ||
try { | ||
resp = await UtilService.addProductToShipment(payload) | ||
if(!hasError(resp)) { | ||
showToast(translate("Product added successfully")); | ||
await this.store.dispatch('util/fetchShipmentItems', { shipmentId: this.shipmentId }); | ||
} else { | ||
throw resp.data; | ||
} | ||
} catch(err) { | ||
showToast(translate("Failed to add product to shipment")) | ||
logger.error(err) | ||
} | ||
}, | ||
closeModal() { | ||
modalController.dismiss({ dismissed: true }); | ||
}, | ||
handleInput() { | ||
if(!this.queryString) { | ||
this.isSearching = false; | ||
store.dispatch("product/clearProducts"); | ||
} | ||
}, | ||
}, | ||
setup() { | ||
const store = useStore(); | ||
const router = useRouter(); | ||
const productIdentificationStore = useProductIdentificationStore(); | ||
let productIdentificationPref = computed(() => productIdentificationStore.getProductIdentificationPref); | ||
return { | ||
closeOutline, | ||
checkmarkCircle, | ||
translate, | ||
store, | ||
getProductIdentificationValue, | ||
productIdentificationPref, | ||
router | ||
} | ||
} | ||
}) | ||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
<template> | ||
<ion-content> | ||
<ion-list> | ||
<ion-list-header>{{ dataManagerLog.logId }}</ion-list-header> | ||
<ion-item button @click="downloadFile('logFile')"> | ||
<ion-label>{{ translate("Log file") }}</ion-label> | ||
</ion-item> | ||
<ion-item button @click="downloadFile('uploadedFile')"> | ||
<ion-label>{{ translate("Uploaded file") }}</ion-label> | ||
</ion-item> | ||
<ion-item button :disabled="!dataManagerLog?.errorRecordContentId" lines="none" @click="downloadFile('failedRecords')"> | ||
<ion-label>{{ translate("Failed records") }}</ion-label> | ||
</ion-item> | ||
</ion-list> | ||
</ion-content> | ||
</template> | ||
|
||
<script lang="ts"> | ||
import { | ||
IonContent, | ||
IonItem, | ||
IonLabel, | ||
IonList, | ||
IonListHeader, | ||
popoverController | ||
} from "@ionic/vue"; | ||
import { defineComponent } from "vue"; | ||
import { translate } from "@hotwax/dxp-components"; | ||
import { UtilService } from "@/services/UtilService"; | ||
import { saveDataFile, showToast } from '@/utils'; | ||
import logger from "@/logger"; | ||
export default defineComponent({ | ||
name: "DownloadLogsFilePopover", | ||
components: { | ||
IonContent, | ||
IonItem, | ||
IonLabel, | ||
IonList, | ||
IonListHeader | ||
}, | ||
props: ["dataManagerLog"], | ||
methods: { | ||
async downloadFile(type: string) { | ||
let dataResource = {} as any; | ||
if(type === 'logFile') { | ||
dataResource.dataResourceId = this.dataManagerLog.logFileDataResourceId | ||
dataResource.name = this.dataManagerLog.logFileContentName | ||
} else if(type === 'uploadedFile') { | ||
dataResource.name = this.dataManagerLog.contentName | ||
dataResource.dataResourceId = this.dataManagerLog.dataResourceId | ||
} else if(type === 'failedRecords') { | ||
dataResource.dataResourceId = this.dataManagerLog.errorRecordDataResourceId | ||
dataResource.name = this.dataManagerLog.errorRecordContentName | ||
} | ||
if(dataResource.dataResourceId) { | ||
try { | ||
const response = await UtilService.fetchFileData({ | ||
dataResourceId: dataResource.dataResourceId | ||
}); | ||
saveDataFile(response.data, dataResource.name); | ||
} catch(error) { | ||
showToast(translate("Error downloading file")) | ||
logger.error(error) | ||
} | ||
} | ||
popoverController.dismiss(); | ||
} | ||
}, | ||
setup() { | ||
return { | ||
translate | ||
} | ||
} | ||
}); | ||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
<template> | ||
<ion-header> | ||
<ion-toolbar> | ||
<ion-buttons slot="start"> | ||
<ion-button @click="closeModal"> | ||
<ion-icon :icon="close" /> | ||
</ion-button> | ||
</ion-buttons> | ||
<ion-title>{{ translate("Help") }}</ion-title> | ||
</ion-toolbar> | ||
</ion-header> | ||
|
||
<ion-content> | ||
<ion-list> | ||
<ion-item lines="full"> | ||
<ion-label>{{ translate("Sample CSV") }}</ion-label> | ||
<ion-button fill="outline" @click="downloadSampleCsv"> | ||
<ion-icon slot="start" :icon="downloadOutline" /> | ||
{{ translate("Download") }} | ||
</ion-button> | ||
</ion-item> | ||
|
||
<ion-item> | ||
<ion-label>{{ translate("Product: Select the type of product identification used in the CSV. If the selected identification is not available on a row in the uploaded CSV, it will be skipped and presented in an error file.") }}</ion-label> | ||
</ion-item> | ||
|
||
<ion-item> | ||
<ion-label>{{ translate("Facility: Use the internal ID of facilities in HotWax. Select external ID option if the uploaded CSV file is coming from a system like an ERP.") }}</ion-label> | ||
</ion-item> | ||
|
||
<ion-item> | ||
<ion-label>{{ translate("Quantity: The amount to adjust the inventory by or the inventory level the inventory should be set to.") }}</ion-label> | ||
</ion-item> | ||
</ion-list> | ||
</ion-content> | ||
</template> | ||
|
||
<script lang="ts"> | ||
import { | ||
IonButtons, | ||
IonButton, | ||
IonContent, | ||
IonHeader, | ||
IonItem, | ||
IonIcon, | ||
IonLabel, | ||
IonList, | ||
IonTitle, | ||
IonToolbar, | ||
modalController | ||
} from "@ionic/vue"; | ||
import { defineComponent } from "vue"; | ||
import { close, downloadOutline } from "ionicons/icons"; | ||
import { translate } from "@hotwax/dxp-components"; | ||
import { jsonToCsv } from "@/utils"; | ||
export default defineComponent({ | ||
name: "HelpModal", | ||
components: { | ||
IonButtons, | ||
IonButton, | ||
IonContent, | ||
IonHeader, | ||
IonIcon, | ||
IonItem, | ||
IonLabel, | ||
IonList, | ||
IonTitle, | ||
IonToolbar | ||
}, | ||
data() { | ||
return { | ||
sampleData: [{ identification: "", quantity: "", facilityId: "" }] | ||
} | ||
}, | ||
methods: { | ||
closeModal() { | ||
modalController.dismiss({ dismissed: true }); | ||
}, | ||
downloadSampleCsv() { | ||
jsonToCsv(this.sampleData, { | ||
download: true, | ||
name: "Sample CSV.csv" | ||
}) | ||
} | ||
}, | ||
setup() { | ||
return { | ||
close, | ||
downloadOutline, | ||
translate | ||
}; | ||
} | ||
}) | ||
</script> |
Oops, something went wrong.