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: Pagination & searching on the landing pages of Admin side(#531) #537

Merged
merged 10 commits into from
Dec 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion src/components/Filters.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@
<ion-checkbox :value="query.noFacility" @ionChange="updateQuery('noFacility', $event.detail.checked)">{{ translate("No facility") }}</ion-checkbox>
</ion-item>

<ion-item lines="none">
<ion-icon slot="start" :icon="swapVerticalOutline" />
<ion-select :label="translate('Sort by')" :value="query.sortBy" @ionChange="updateQuery('sortBy', $event.detail.value)" interface="popover">
<ion-select-option value="dueDate desc">{{ translate("Farthest due") }}</ion-select-option>
<ion-select-option value="dueDate asc">{{ translate("Nearest due") }}</ion-select-option>
<ion-select-option value="countImportName asc">{{ translate("Name - A to Z") }}</ion-select-option>
<ion-select-option value="countImportName desc">{{ translate("Name - Z to A") }}</ion-select-option>
</ion-select>
</ion-item>

<template v-if="showAdditionalFilters().selectedFacilities">
<ion-item v-for="facilityId in query.facilityIds" :key="facilityId">
<ion-label>{{ getFacilityName(facilityId) }}</ion-label>
Expand Down Expand Up @@ -137,7 +147,7 @@ import {
IonToolbar
} from "@ionic/vue";
import { computed, ref } from "vue";
import { closeCircleOutline, businessOutline, gitBranchOutline, gitPullRequestOutline, locateOutline } from "ionicons/icons";
import { closeCircleOutline, businessOutline, gitBranchOutline, gitPullRequestOutline, locateOutline, swapVerticalOutline } from "ionicons/icons";
import { translate } from '@/i18n'
import store from "@/store";
import router from "@/router";
Expand Down
5 changes: 5 additions & 0 deletions src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@
"Failed to submit cycle count for review": "Failed to submit cycle count for review",
"Failed to update items": "Failed to update items",
"Failed to update cycle count information": "Failed to update cycle count information",
"Farthest due": "Farthest due",
"Fetching cycle counts...": "Fetching cycle counts...",
"Field mapping name": "Field mapping name",
"File uploaded successfully.": "File uploaded successfully.",
Expand Down Expand Up @@ -161,8 +162,11 @@
"Make sure you've reviewed the products and their counts before uploading them for review": "Make sure you've reviewed the products and their counts before uploading them for review",
"Map all required fields": "Map all required fields",
"Mapping name": "Mapping name",
"Name - A to Z": "Name - A to Z",
"Name - Z to A": "Name - Z to A",
"New Count": "New Count",
"New mapping": "New mapping",
"Nearest due": "Nearest due",
"No cycle counts found": "No cycle counts found",
"No data found": "No data found",
"No items found": "No items found",
Expand Down Expand Up @@ -272,6 +276,7 @@
"Something went wrong": "Something went wrong",
"Something went wrong, please try again": "Something went wrong, please try again",
"Something went wrong while login. Please contact administrator": "Something went wrong while login. Please contact administrator.",
"Sort by": "Sort by",
"Specify which facility you want to operate from. Order, inventory and other configuration data will be specific to the facility you select.": "Specify which facility you want to operate from. Order, inventory and other configuration data will be specific to the facility you select.",
"Status": "Status",
"STAY": "STAY",
Expand Down
5 changes: 4 additions & 1 deletion src/store/modules/count/CountState.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
export default interface CountState {
list: Array<any>,
total: number,
isScrollable: boolean,
query: {
facilityIds: Array<string>,
noFacility: boolean
noFacility: boolean,
queryString: string,
sortBy: string
},
stats: any;
cycleCounts: any;
Expand Down
40 changes: 29 additions & 11 deletions src/store/modules/count/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,24 @@
import * as types from "./mutation-types"
import { CountService } from "@/services/CountService"
import { hasError, showToast } from "@/utils"
import emitter from "@/event-bus"
import { translate } from "@/i18n"
import router from "@/router"
import logger from "@/logger";
import { DateTime } from "luxon"

const actions: ActionTree<CountState, RootState> = {
async fetchCycleCounts({ commit, dispatch, state }, payload) {
emitter.emit("presentLoader", { message: "Fetching cycle counts...", backdropDismiss: false })
let counts: Array<any> = [], total = 0;
let counts = state.list ? JSON.parse(JSON.stringify(state.list)) : [], total = 0;
let isScrollable = true

const params = {
...payload,
pageSize: 200
}
// TODO: Currently, the search functionality works only on the count name. Once the API supports searching across
// multiple fields, we should include the count ID in the search parameters.
if(state.query.queryString.length) {
params["countImportName"] = state.query.queryString
params["countImportName_op"] = "contains"
}

if(state.query.facilityIds.length) {
Expand All @@ -34,22 +38,32 @@
}
}

if(state.query.sortBy) {
params["orderByField"] = state.query.sortBy
}

try {
const resp = await CountService.fetchCycleCounts(params);

if(!hasError(resp) && resp.data.length > 0) {
counts = resp.data
if(payload.pageIndex && payload.pageIndex > 0) {
counts = counts.concat(resp.data)
} else {
counts = resp.data
}
total = resp.data.length

dispatch("fetchCycleCountStats", counts.map((count) => count.inventoryCountImportId))
dispatch("fetchCycleCountStats", counts.map((count: any) => count.inventoryCountImportId))
// Determine if more data can be fetched
isScrollable = resp.data.length >= payload.pageSize
} else {
if (payload.pageIndex > 0) isScrollable = false
throw "Failed to fetch the counts"
}
} catch(err) {
isScrollable = false
if(payload.pageIndex == 0) counts = []
logger.error(err)
}
commit(types.COUNT_LIST_UPDATED, { counts, total })
emitter.emit("dismissLoader")
commit(types.COUNT_LIST_UPDATED, { counts, total , isScrollable })
},

async fetchCycleCountStats({ commit }, inventoryCountImportIds) {
Expand Down Expand Up @@ -116,7 +130,11 @@
} else if(router.currentRoute.value.name === "Closed") {
statusId = "INV_COUNT_COMPLETED"
}
dispatch("fetchCycleCounts", { statusId })
dispatch("fetchCycleCounts", { pageSize: process.env.VUE_APP_VIEW_SIZE, pageIndex: 0, statusId })
},

async updateQueryString({ commit }, payload) {
commit(types.COUNT_QUERY_UPDATED, payload)
},

async clearQuery({ commit }) {
Expand Down Expand Up @@ -184,7 +202,7 @@
commit(types.COUNT_ITEMS_UPDATED, [])
},

async fetchCycleCountImportSystemMessages({commit} ,payload) {

Check warning on line 205 in src/store/modules/count/actions.ts

View workflow job for this annotation

GitHub Actions / call-workflow-in-another-repo / reusable_workflow_job (18.x)

'payload' is defined but never used

Check warning on line 205 in src/store/modules/count/actions.ts

View workflow job for this annotation

GitHub Actions / call-workflow-in-another-repo / reusable_workflow_job (20.x)

'payload' is defined but never used
let systemMessages;
try {
const twentyFourHoursEarlier = DateTime.now().minus({ hours: 24 });
Expand Down
3 changes: 3 additions & 0 deletions src/store/modules/count/getters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ const getters: GetterTree<CountState, RootState> = {
getCycleCountsList(state) {
return state.cycleCounts.list ? JSON.parse(JSON.stringify(state.cycleCounts.list)) : []
},
isCycleCountListScrollable(state) {
return state.isScrollable
},
isCycleCountScrollable(state) {
return state.cycleCounts.isScrollable
},
Expand Down
5 changes: 4 additions & 1 deletion src/store/modules/count/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@ const countModule: Module<CountState, RootState> = {
state: {
list: [],
total: 0,
isScrollable: true,
query: {
facilityIds: [],
noFacility: false
noFacility: false,
queryString: '',
sortBy: 'dueDate desc'
},
stats: {},
cycleCountImportSystemMessages:[],
Expand Down
5 changes: 4 additions & 1 deletion src/store/modules/count/mutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,17 @@ const mutations: MutationTree <CountState> = {
[types.COUNT_LIST_UPDATED](state, payload) {
state.list = payload.counts
state.total = payload.total
state.isScrollable = payload.isScrollable;
},
[types.COUNT_QUERY_UPDATED](state, payload) {
(state.query as any)[payload.key] = payload.value
},
[types.COUNT_QUERY_CLEARED](state) {
state.query = {
facilityIds: [],
noFacility: false
noFacility: false,
queryString: '',
sortBy: 'dueDate desc'
}
},
[types.COUNT_STATS_UPDATED](state, payload) {
Expand Down
5 changes: 5 additions & 0 deletions src/theme/variables.css
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,11 @@ http://ionicframework.com/docs/theming/ */
.list-item {
padding: var(--spacer-sm) 0;
}

ion-searchbar.searchbar {
padding-top: var(--spacer-base);
padding-inline: var(--spacer-sm)
}
}

.empty-state {
Expand Down
62 changes: 56 additions & 6 deletions src/views/Assigned.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
</ion-toolbar>
</ion-header>

<ion-content id="filter">
<ion-content ref="contentRef" :scroll-events="true" @ionScroll="enableScrolling()" id="filter">
<ion-searchbar class="searchbar" v-model="query.queryString" @keyup.enter="updateQueryString('queryString', $event.target.value)" />
<p v-if="!cycleCounts.length" class="empty-state">
{{ translate("No cycle counts found") }}
</p>
Expand Down Expand Up @@ -47,31 +48,80 @@
</ion-item>
</div>
</ion-list>

<ion-infinite-scroll ref="infiniteScrollRef" v-show="isScrollable" threshold="100px" @ionInfinite="loadMoreCycleCounts($event)">
<ion-infinite-scroll-content loading-spinner="crescent" :loading-text="translate('Loading')" />
</ion-infinite-scroll>
</ion-content>
</ion-page>
</template>

<script setup lang="ts">
import { computed } from "vue";
import { computed, ref } from "vue";
import { translate } from '@/i18n'
import { filterOutline, storefrontOutline } from "ionicons/icons";
import { IonBadge, IonButtons, IonChip, IonContent, IonHeader, IonIcon, IonItem, IonLabel, IonList, IonMenuButton, IonPage, IonTitle, IonToolbar, onIonViewDidEnter, onIonViewWillLeave } from "@ionic/vue";
import { IonBadge, IonButtons, IonChip, IonContent, IonHeader, IonIcon, IonItem, IonInfiniteScroll, IonInfiniteScrollContent, IonLabel, IonList, IonMenuButton, IonPage, IonSearchbar, IonTitle, IonToolbar, onIonViewDidEnter, onIonViewWillLeave } from "@ionic/vue";
import store from "@/store"
import { getCycleCountStats, getDateWithOrdinalSuffix, getDerivedStatusForCount, getFacilityName } from "@/utils"
import Filters from "@/components/Filters.vue"
import router from "@/router"

const cycleCounts = computed(() => store.getters["count/getCounts"])
const isScrollable = computed(() => store.getters["count/isCycleCountListScrollable"])
const query = computed(() => store.getters["count/getQuery"])

const isScrollingEnabled = ref(false);
const contentRef = ref({}) as any
const infiniteScrollRef = ref({}) as any

onIonViewDidEnter(async () => {
await store.dispatch("count/fetchCycleCounts", {
statusId: "INV_COUNT_ASSIGNED"
})
await fetchAssignedCycleCount();
})

onIonViewWillLeave(async () => {
await store.dispatch("count/clearCycleCountList")
})

function enableScrolling() {
const parentElement = contentRef.value.$el
const scrollEl = parentElement.shadowRoot.querySelector("main[part='scroll']")
let scrollHeight = scrollEl.scrollHeight, infiniteHeight = infiniteScrollRef?.value?.$el?.offsetHeight, scrollTop = scrollEl.scrollTop, threshold = 100, height = scrollEl.offsetHeight
const distanceFromInfinite = scrollHeight - infiniteHeight - scrollTop - threshold - height
if(distanceFromInfinite < 0) {
isScrollingEnabled.value = false;
} else {
isScrollingEnabled.value = true;
}
}

async function updateQueryString(key: string, value: any) {
await store.dispatch("count/updateQueryString", { key, value })
fetchAssignedCycleCount();
}

async function loadMoreCycleCounts(event: any) {
if(!(isScrollingEnabled.value && isScrollable.value)) {
await event.target.complete();
}
fetchAssignedCycleCount(
undefined,
Math.ceil(
cycleCounts.value?.length / (process.env.VUE_APP_VIEW_SIZE as any)
).toString()
).then(async () => {
await event.target.complete()})
}

async function fetchAssignedCycleCount(vSize?: any, vIndex?: any) {
const pageSize = vSize ? vSize : process.env.VUE_APP_VIEW_SIZE;
const pageIndex = vIndex ? vIndex : 0;
const payload = {
pageSize,
pageIndex,
statusId: "INV_COUNT_ASSIGNED"
}
await store.dispatch("count/fetchCycleCounts", payload)
}
</script>

<style scoped>
Expand Down
Loading
Loading