Skip to content

Commit

Permalink
Merge pull request #204 from ymaheshwari1/fulfillment/#192
Browse files Browse the repository at this point in the history
Implemented: support to save mappings and apply already available mappings(#192)
  • Loading branch information
adityasharma7 authored Jul 25, 2023
2 parents fe5b43b + 3e40357 commit 89d1dae
Show file tree
Hide file tree
Showing 21 changed files with 1,006 additions and 48 deletions.
4 changes: 3 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ VUE_APP_BASE_URL=
VUE_APP_PERMISSION_ID=
VUE_APP_ALIAS={}
VUE_APP_DEFAULT_LOG_LEVEL="error"
VUE_APP_UPLD_IMP_ORD={"orderId": { "label": "Order ID", "required": true }, "facilityId": { "label": "Facility ID", "required": true },"trackingCode": { "label": "Tracking Code", "required": true }}
VUE_APP_MAPPING_TYPES={"IMPORD": "IMP_ORD_MAPPING_PREF","EXPORD": "EXP_PKD_ORD_MAPPING_PREF"}
VUE_APP_MAPPING_IMPORD={"orderId": { "label": "Order ID", "value": "" }, "facilityId": { "label": "Facility ID", "value": "" },"trackingCode": { "label": "Tracking Code", "value": "" }}
VUE_APP_MAPPING_EXPORD={"shipment-id": { "label": "Shipment ID", "value": "" }, "order-id": { "label": "Order ID", "value": "" },"to-name": { "label": "To Name", "value": "" },"address1": { "label": "Address 1", "value": "" },"address2": { "label": "Address 2", "value": "" },"city": { "label": "City", "value": "" },"state": { "label": "State", "value": "" },"zip-code": { "label": "Zip Code", "value": "" },"country-code": { "label": "Country Code", "value": "" },"full-to-address": { "label": "Full Address", "value": "" },"phone-number": { "label": "Phone Number", "value": "" },"email-address": { "label": "Email", "value": "" },"weight": { "label": "Weight", "value": "" },"quantity": { "label": "Quantity", "value": "" },"product-name": { "label": "Product Name", "value": "" },"product-sku": { "label": "Product Sku", "value": "" },"shipping-method": { "label": "Shipping Method", "value": "" },"facility-name": { "label": "Facility Name", "value": "" },"facility-address1": { "label": "Facility Address 1", "value": "" },"facility-address2": { "label": "Facility Address 2", "value": "" },"facility-city": { "label": "Facility City", "value": "" },"facility-state": { "label": "Facility State", "value": "" },"facility-zip-code": { "label": "Facility Zip Code", "value": "" },"facility-phone": { "label": "Facility Phone", "value": "" },"facility-full-address": { "label": "Facility Full Address", "value": "" }}
27 changes: 26 additions & 1 deletion src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
</template>

<script lang="ts">
import { IonApp, IonRouterOutlet, IonSplitPane } from '@ionic/vue';
import { createAnimation, IonApp, IonRouterOutlet, IonSplitPane } from '@ionic/vue';
import { defineComponent } from 'vue';
import Menu from '@/components/Menu.vue';
import { loadingController } from '@ionic/vue';
Expand Down Expand Up @@ -60,6 +60,29 @@ export default defineComponent({
async unauthorised() {
this.store.dispatch("user/logout");
this.router.push("/login")
},
playAnimation() {
const aside = document.querySelector('aside') as Element
const main = document.querySelector('main') as Element
const revealAnimation = createAnimation()
.addElement(aside)
.duration(1500)
.easing('ease')
.keyframes([
{ offset: 0, flex: '0', opacity: '0' },
{ offset: 0.5, flex: '1', opacity: '0' },
{ offset: 1, flex: '1', opacity: '1' }
])
const gapAnimation = createAnimation()
.addElement(main)
.duration(500)
.fromTo('gap', '0', 'var(--spacer-2xl)');
createAnimation()
.addAnimation([gapAnimation, revealAnimation])
.play();
}
},
created() {
Expand Down Expand Up @@ -87,6 +110,7 @@ export default defineComponent({
});
emitter.on('presentLoader', this.presentLoader);
emitter.on('dismissLoader', this.dismissLoader);
emitter.on('playAnimation', this.playAnimation);
// Handles case when user resumes or reloads the app
// Luxon timezzone should be set with the user's selected timezone
Expand All @@ -97,6 +121,7 @@ export default defineComponent({
unmounted() {
emitter.off('presentLoader', this.presentLoader);
emitter.off('dismissLoader', this.dismissLoader);
emitter.off('playAnimation', this.playAnimation);
resetConfig()
},
setup() {
Expand Down
143 changes: 143 additions & 0 deletions src/components/CreateMappingModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
<template>
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-button @click="closeModal">
<ion-icon :icon="close" />
</ion-button>
</ion-buttons>
<ion-title>{{ $t("CSV Mapping") }}</ion-title>
</ion-toolbar>
</ion-header>

<ion-item>
<ion-label>{{ $t("Mapping name") }}</ion-label>
<ion-input :placeholder="$t('Field mapping name')" v-model="mappingName" />
</ion-item>

<ion-content class="ion-padding">
<div>
<ion-list>
<ion-item :key="field" v-for="(fieldValues, field) in fieldMapping">
<ion-label>{{ $t(fieldValues.label) }}</ion-label>
<ion-input v-if="mappingType === 'EXPORD'" slot="end" v-model="fieldValues.value"></ion-input>
<ion-select v-else interface="popover" :placeholder = "$t('Select')" v-model="fieldValues.value">
<ion-select-option :key="index" v-for="(prop, index) in fileColumns">{{ prop }}</ion-select-option>
</ion-select>
</ion-item>
</ion-list>
</div>
<ion-fab vertical="bottom" horizontal="end" slot="fixed">
<ion-fab-button @click="saveMapping">
<ion-icon :icon="saveOutline" />
</ion-fab-button>
</ion-fab>
</ion-content>
</template>

<script lang="ts">
import {
IonButtons,
IonButton,
IonContent,
IonHeader,
IonIcon,
IonFab,
IonFabButton,
IonInput,
IonTitle,
IonToolbar,
IonLabel,
IonItem,
IonList,
IonSelect,
IonSelectOption,
modalController
} from "@ionic/vue";
import { defineComponent } from "vue";
import { close, save, saveOutline } from "ionicons/icons";
import { useStore, mapGetters } from "vuex";
import { showToast } from '@/utils';
import { translate } from "@/i18n";
export default defineComponent({
name: "CreateMappingModal",
components: {
IonButtons,
IonButton,
IonContent,
IonFab,
IonFabButton,
IonHeader,
IonIcon,
IonInput,
IonSelect,
IonSelectOption,
IonTitle,
IonToolbar,
IonLabel,
IonItem,
IonList
},
data() {
return {
mappingName: "",
fieldMapping: {} as any,
fileColumns: [] as any
}
},
props: ["content", "mappings", "mappingType"],
mounted() {
// mappings needs to be in format { <key>: { value: <value>, label: <label>, isSelected | optional: <boolean> }}
this.fieldMapping = JSON.parse(JSON.stringify(this.mappings));
this.fileColumns = Object.keys(this.content[0]);
},
computed: {
...mapGetters({
fieldMappings: 'user/getFieldMappings'
})
},
methods: {
closeModal() {
modalController.dismiss({ dismissed: true });
},
async saveMapping() {
if(!this.mappingName) {
showToast(translate("Enter mapping name"));
return
}
if (!this.areAllFieldsSelected()) {
showToast(translate("Map all fields"));
return
}
const id = this.generateUniqueMappingPrefId();
// removing label from mappings as we don't need to save label with the mappings as it will just increase the size of the value
Object.keys(this.fieldMapping).map((mapping) => {
this.fieldMapping[mapping] && delete this.fieldMapping[mapping].label
return;
})
await this.store.dispatch("user/createFieldMapping", { id, name: this.mappingName, value: this.fieldMapping, mappingType: this.mappingType })
this.closeModal();
},
areAllFieldsSelected() {
return Object.values(this.fieldMapping).every((field: any) => field.value !== "");
},
//Todo: Generating unique identifiers as we are currently storing in local storage. Need to remove it as we will be storing data on server.
generateUniqueMappingPrefId(): any {
const id = Math.floor(Math.random() * 1000);
return !this.fieldMappings[id] ? id : this.generateUniqueMappingPrefId();
}
},
setup() {
const store = useStore();
return {
close,
save,
saveOutline,
store
};
}
});
</script>
7 changes: 5 additions & 2 deletions src/components/CustomFieldModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,17 @@ export default defineComponent({
value: ''
}
},
props: ["customFields"],
methods: {
closeModal() {
modalController.dismiss({ dismissed: true});
},
saveCustomField() {
const fieldKey = this.key.trim();
if(!fieldKey) {
showToast(translate('Please enter a valid key'))
const errorMessage = fieldKey ? this.customFields[fieldKey] ? 'Please enter a unique key' : '' : 'Please enter a valid key'
if(errorMessage) {
showToast(translate(errorMessage))
return;
}
modalController.dismiss({ dismissed: true, value: { key: fieldKey, value: this.value } });
Expand Down
142 changes: 142 additions & 0 deletions src/components/MappingConfiguration.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
<template>
<section>
<ion-item>
<ion-label>{{ $t("Mapping name") }}</ion-label>
<ion-input v-model="currentMapping.name" />
</ion-item>

<ion-list>
<ion-item :key="field" v-for="(fieldValues, field) in currentMapping.value">
<ion-label>{{ fields[field] ? fields[field].label : field }}</ion-label>
<ion-input v-model="fieldValues.value" />
</ion-item>
</ion-list>

<div class="ion-padding-top actions desktop-only">
<div>
<ion-button size="small" @click="updateMapping()">
<ion-icon slot="start" :icon="saveOutline"/>
{{ $t("Save Changes") }}
</ion-button>
<ion-button size="small" fill="outline" color="danger" @click="deleteMapping()">
<ion-icon slot="start" :icon="trashOutline" />
{{ $t("Delete mapping") }}
</ion-button>
</div>
</div>

<div class="ion-padding-top actions mobile-only">
<ion-button expand="block" @click="updateMapping()">
<ion-icon slot="start" :icon="saveOutline"/>
{{ $t("Save Changes") }}
</ion-button>
<ion-button fill="outline" color="danger" expand="block" @click="deleteMapping()">
<ion-icon slot="start" :icon="trashOutline" />
{{ $t("Delete mapping") }}
</ion-button>
</div>
</section>
</template>

<script lang="ts">
import {
alertController,
IonButton,
IonIcon,
IonInput,
IonLabel,
IonItem,
IonList
} from "@ionic/vue";
import { defineComponent } from "vue";
import { close, save, saveOutline, trashOutline } from "ionicons/icons";
import { useStore, mapGetters } from "vuex";
import { showToast } from "@/utils";
import { translate } from "@/i18n";
export default defineComponent({
name: "MappingConfiguration",
components: {
IonButton,
IonIcon,
IonInput,
IonLabel,
IonItem,
IonList
},
computed: {
...mapGetters({
currentMapping: 'user/getCurrentMapping'
})
},
data() {
return {
fields: {} as any
}
},
mounted() {
const fields = process.env["VUE_APP_MAPPING_" + this.currentMapping.mappingType];
this.fields = fields ? JSON.parse(fields) : {};
},
methods: {
async deleteMapping() {
const message = this.$t("Are you sure you want to delete this CSV mapping? This action cannot be undone.");
const alert = await alertController.create({
header: this.$t("Delete mapping"),
message,
buttons: [
{
text: this.$t("Cancel"),
},
{
text: this.$t("Delete"),
handler: () => {
this.store.dispatch("user/deleteFieldMapping", this.currentMapping)}
}
],
});
return alert.present();
},
areAllFieldsSelected() {
return Object.values(this.currentMapping.value).every((field: any) => field.value !== "");
},
async updateMapping() {
if(!this.currentMapping.name) {
showToast(translate("Enter mapping name"));
return;
}
if (!this.areAllFieldsSelected()) {
showToast(translate("Map all fields"));
return;
}
const message = this.$t("Are you sure you want to update this CSV mapping? This action cannot be undone.");
const alert = await alertController.create({
header: this.$t("Update mapping"),
message,
buttons: [
{
text: this.$t("Cancel"),
},
{
text: this.$t("Update"),
handler: () => {
this.store.dispatch('user/updateFieldMapping', this.currentMapping)
}
}
],
});
return alert.present();
}
},
setup() {
const store = useStore();
return {
close,
save,
saveOutline,
trashOutline,
store
};
}
});
</script>
2 changes: 1 addition & 1 deletion src/components/Menu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export default defineComponent({
url: "/exim",
iosIcon: swapVerticalOutline,
mdIcon: swapVerticalOutline,
childRoutes: ["/download-packed-orders", "/upload-import-orders"] // defined child routes as to enable the correct menu when we are on a route that is not listed in the menu
childRoutes: ["/download-packed-orders", "/upload-import-orders", "/saved-mappings"] // defined child routes as to enable the correct menu when we are on a route that is not listed in the menu
},
{
title: "Settings",
Expand Down
Loading

0 comments on commit 89d1dae

Please sign in to comment.