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

Implemented: centralized facility selector(#228) #320

Merged
merged 7 commits into from
Oct 17, 2024
162 changes: 162 additions & 0 deletions src/components/DxpFacilitySwitcher.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
<template>
<ion-card>
<ion-card-header>
<ion-card-title>
{{ $t('Facility') }}
</ion-card-title>
</ion-card-header>
<ion-card-content>
{{ $t('Specify which facility you want to operate from. Order, inventory and other configuration data will be specific to the facility you select.') }}
</ion-card-content>
<ion-item lines="none">
<ion-label>
{{ currentFacility.facilityName }}
<p>{{ currentFacility.facilityId }}</p>
</ion-label>
<ion-button id="open-facility-modal" slot="end" fill="outline" color="dark">{{ $t('Change')}}</ion-button>
</ion-item>
</ion-card>
<!-- Using inline modal(as recommended by ionic), also using it inline as the component inside modal is not getting mounted when using modalController -->
<ion-modal ref="facilityModal" trigger="open-facility-modal" @didPresent="loadFacilities()" @didDismiss="clearSearch()">
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-button @click="closeModal">
<ion-icon :icon="closeOutline"/>
</ion-button>
</ion-buttons>
<ion-title>{{ $t("Select Facility") }}</ion-title>
</ion-toolbar>
</ion-header>
<ion-toolbar>
<ion-searchbar @ionFocus="selectSearchBarText($event)" :placeholder="$t('Search facilities')" v-model="queryString" @keyup.enter="queryString = $event.target.value; findFacility()" @keydown="preventSpecialCharacters($event)"/>
</ion-toolbar>
<ion-content>
<ion-radio-group v-model="selectedFacilityId">
<ion-list>
<!-- Loading state -->
<div class="empty-state" v-if="isLoading">
<ion-item lines="none">
<ion-spinner color="secondary" name="crescent" slot="start" />
{{ $t("Fetching facilities") }}
</ion-item>
</div>
<!-- Empty state -->
<div class="empty-state" v-else-if="!filteredFacilities.length">
<p>{{ $t("No facilities found") }}</p>
</div>
<div v-else>
<ion-item v-for="facility in filteredFacilities" :key="facility.facilityId">
<ion-radio label-placement="end" justify="start" :value="facility.facilityId">
<ion-label>
{{ facility.facilityName }}
<p>{{ facility.facilityId }}</p>
</ion-label>
</ion-radio>
</ion-item>
</div>
</ion-list>
</ion-radio-group>

<ion-fab vertical="bottom" horizontal="end" slot="fixed">
<ion-fab-button :disabled="selectedFacilityId === currentFacility.facilityId" @click="updateFacility">
<ion-icon :icon="saveOutline" />
</ion-fab-button>
</ion-fab>
</ion-content>
</ion-modal>
</template>

<script setup lang="ts">
import {
IonButton,
IonButtons,
IonCard,
IonCardContent,
IonCardHeader,
IonCardTitle,
IonContent,
IonFab,
IonFabButton,
IonHeader,
IonIcon,
IonItem,
IonLabel,
IonList,
IonModal,
IonRadio,
IonRadioGroup,
IonSearchbar,
IonSpinner,
IonTitle,
IonToolbar
} from '@ionic/vue';
import { closeOutline, saveOutline } from "ionicons/icons";
import { useUserStore } from 'src/store/user';
import { computed, ref } from 'vue';

const userStore = useUserStore();

const facilities = computed(() => userStore.getFacilites)
const currentFacility = computed(() => userStore.getCurrentFacility)

const facilityModal = ref()
const queryString = ref('')
const isLoading = ref(true);
const filteredFacilities = ref([])
const selectedFacilityId = ref(currentFacility.value.facilityId)

const emit = defineEmits(["updateFacility"])

const closeModal = () => {
facilityModal.value.$el.dismiss(null, 'cancel');
}

function loadFacilities() {
filteredFacilities.value = facilities.value;
isLoading.value = false;
}

const findFacility = () => {
ymaheshwari1 marked this conversation as resolved.
Show resolved Hide resolved
isLoading.value = true
const searchedString = queryString.value.trim().toLowerCase();
if(searchedString) {
ymaheshwari1 marked this conversation as resolved.
Show resolved Hide resolved
filteredFacilities.value = facilities.value.filter((facility: any) =>
facility.facilityName.toLowerCase().includes(searchedString) ||
facility.facilityId.toLowerCase().includes(searchedString)
);
} else {
filteredFacilities.value = facilities.value;
}
isLoading.value = false
}

async function selectSearchBarText(event: any) {
const element = await event.target.getInputElement()
element.select();
}

function preventSpecialCharacters($event: any) {
// Searching special characters fails the API, hence, they must be omitted
if(/[`!@#$%^&*()_+\-=\\|,.<>?~]/.test($event.key)) $event.preventDefault();
}

async function updateFacility() {
const selectedFacility = facilities.value.find((facility: any) => facility.facilityId === selectedFacilityId.value)
await userStore.setFacilityPreference(selectedFacility)
emit('updateFacility', selectedFacility);
closeModal();
}

function clearSearch() {
queryString.value = ''
filteredFacilities.value = []
isLoading.value = true
}
</script>

<style scoped>
ion-content {
--padding-bottom: 80px;
}
</style>
1 change: 1 addition & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import '@ionic/vue/css/flex-utils.css';
import '@ionic/vue/css/display.css';

export { default as DxpAppVersionInfo } from './DxpAppVersionInfo.vue';
export { default as DxpFacilitySwitcher } from './DxpFacilitySwitcher.vue'
export { default as DxpGitBookSearch } from './DxpGitBookSearch.vue';
export { default as DxpImage } from './DxpImage.vue';
export { default as DxpLanguageSwitcher } from './DxpLanguageSwitcher.vue';
Expand Down
9 changes: 8 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ declare var process: any;
import { createPinia } from "pinia";
import { useProductIdentificationStore } from "./store/productIdentification";
import { useAuthStore } from "./store/auth";
import { DxpAppVersionInfo, DxpGitBookSearch, DxpImage, DxpLanguageSwitcher, DxpLogin, DxpMenuFooterNavigation, DxpOmsInstanceNavigator, DxpProductIdentifier, DxpShopifyImg, DxpTimeZoneSwitcher, DxpUserProfile } from "./components";
import { DxpAppVersionInfo, DxpFacilitySwitcher, DxpGitBookSearch, DxpImage, DxpLanguageSwitcher, DxpLogin, DxpMenuFooterNavigation, DxpOmsInstanceNavigator, DxpProductIdentifier, DxpShopifyImg, DxpTimeZoneSwitcher, DxpUserProfile } from "./components";
import { goToOms, getProductIdentificationValue } from "./utils";
import { initialiseFirebaseApp } from "./utils/firebase"
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
Expand All @@ -25,6 +25,7 @@ let loginContext = {} as any
let shopifyImgContext = {} as any
let appContext = {} as any
let productIdentificationContext = {} as any
let facilityContext = {} as any
let notificationContext = {} as any
let gitBookContext = {} as any
let userContext = {} as any
Expand Down Expand Up @@ -67,6 +68,7 @@ export let dxpComponents = {
})

app.component('DxpAppVersionInfo', DxpAppVersionInfo)
app.component('DxpFacilitySwitcher', DxpFacilitySwitcher)
app.component('DxpGitBookSearch', DxpGitBookSearch)
app.component('DxpImage', DxpImage)
app.component('DxpLanguageSwitcher', DxpLanguageSwitcher)
Expand Down Expand Up @@ -96,6 +98,10 @@ export let dxpComponents = {

productIdentificationContext.getProductIdentificationPref = options.getProductIdentificationPref
productIdentificationContext.setProductIdentificationPref = options.setProductIdentificationPref

facilityContext.getUserFacilities = options.getUserFacilities
facilityContext.setUserPreference = options.setUserPreference
facilityContext.getUserPreference = options.getUserPreference

notificationContext.addNotification = options.addNotification
notificationContext.appFirebaseConfig = options.appFirebaseConfig
Expand Down Expand Up @@ -135,6 +141,7 @@ export {
loginContext,
notificationContext,
productIdentificationContext,
facilityContext,
shopifyImgContext,
translate,
useAuthStore,
Expand Down
4 changes: 4 additions & 0 deletions src/store/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ export const useAuthStore = defineStore('userAuth', {
getters: {
getToken: (state) => state.token,
getOms: (state) => state.oms,
getBaseUrl: (state) => {
let baseURL = state.oms
return baseURL.startsWith('http') ? baseURL.includes('/api') ? baseURL : `${baseURL}/api/` : `https://${baseURL}.hotwax.io/api/`;
},
isAuthenticated: (state) => {
let isTokenExpired = false
if (state.token.expiration) {
Expand Down
59 changes: 54 additions & 5 deletions src/store/user.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { defineStore } from "pinia";
import { appContext, i18n, translate, userContext } from "../../src";
import { hasError } from "@hotwax/oms-api";
import { i18n, translate, userContext, useAuthStore } from "../../src";
import { DateTime } from "luxon";
import { showToast } from "src/utils";
import { facilityContext } from "../index";

declare let process: any;

Expand All @@ -12,14 +12,18 @@ export const useUserStore = defineStore('user', {
localeOptions: process.env.VUE_APP_LOCALES ? JSON.parse(process.env.VUE_APP_LOCALES) : { "en-US": "English" },
locale: 'en-US',
currentTimeZoneId: '',
timeZones: []
timeZones: [],
facilities: [],
currentFacility: {} as any
}
},
getters: {
getLocale: (state) => state.locale,
getLocaleOptions: (state) => state.localeOptions,
getTimeZones: (state) => state.timeZones,
getCurrentTimeZone: (state) => state.currentTimeZoneId
getCurrentTimeZone: (state) => state.currentTimeZoneId,
getFacilites: (state) => state.facilities,
getCurrentFacility: (state) => state.currentFacility
},
actions: {
async setLocale(locale: string) {
Expand Down Expand Up @@ -74,7 +78,52 @@ export const useUserStore = defineStore('user', {
},
updateTimeZone(tzId: string) {
this.currentTimeZoneId = tzId
}
},

async getUserFacilities(partyId: any, facilityGroupId: any, isAdminUser: boolean) {
const authStore = useAuthStore();

try {
const response = await facilityContext.getUserFacilities(authStore.getToken.value, authStore.getBaseUrl, partyId, facilityGroupId, isAdminUser);
this.facilities = response;
} catch (error) {
console.error(error);
}
return this.facilities
},

async setFacilityPreference(payload: any) {

try {
await facilityContext.setUserPreference({
userPrefTypeId: 'SELECTED_FACILITY',
userPrefValue: payload.facilityId
})
} catch (error) {
console.error('error', error)
}
this.currentFacility = payload;
},

async getFacilityPreference(userPrefTypeId: any) {
const authStore = useAuthStore();

if (!this.facilities.length) {
return;
}
let preferredFacility = this.facilities[0];

try {
let preferredFacilityId = await facilityContext.getUserPreference(authStore.getToken.value, authStore.getBaseUrl, userPrefTypeId);
if(preferredFacilityId) {
const facility = this.facilities.find((facility: any) => facility.facilityId === preferredFacilityId);
facility && (preferredFacility = facility)
}
} catch (error) {
console.error(error);
}
this.currentFacility = preferredFacility;
},
},
persist: true
})
Loading