diff --git a/composables/useInfiniteScroll.ts b/composables/useInfiniteScroll.ts new file mode 100644 index 0000000..4096aff --- /dev/null +++ b/composables/useInfiniteScroll.ts @@ -0,0 +1,81 @@ +import { ref, computed, onMounted, onUnmounted, watch } from 'vue'; + +/** + * Reusable Infinite Scroll Composable + * @param {import('vue').Ref} sourceData - The full source array of data + * @param {number} pageSize - Number of items to load per "page" + * @param {Object} options - Additional options + * @returns {Object} { visibleItems, targetRef, loadMore, reset } + */ +export function useInfiniteScroll(sourceData, pageSize = 20, options = {}) { + const visibleCount = ref(pageSize); + const targetRef = ref(null); + + // Compute visible items based on current count + const visibleItems = computed(() => { + const data = sourceData.value || []; + return data.slice(0, visibleCount.value); + }); + + // Check if there are more items to load + const hasMore = computed(() => { + const data = sourceData.value || []; + return visibleCount.value < data.length; + }); + + const loadMore = () => { + if (hasMore.value) { + visibleCount.value += pageSize; + } + }; + + const reset = () => { + visibleCount.value = pageSize; + }; + + // Setup IntersectionObserver + let observer = null; + + onMounted(() => { + observer = new IntersectionObserver((entries) => { + const entry = entries[0]; + if (entry.isIntersecting && hasMore.value) { + loadMore(); + } + }, { + root: null, + rootMargin: '100px', // Preload before reaching bottom + threshold: 0.1, + ...options + }); + + if (targetRef.value) { + observer.observe(targetRef.value); + } + }); + + onUnmounted(() => { + if (observer) observer.disconnect(); + }); + + // Watch for targetRef changes (e.g., if valid status changes) + watch(targetRef, (el) => { + if (observer) { + observer.disconnect(); + if (el) observer.observe(el); + } + }); + + // Reset when source data changes significantly (optional, depending on use case) + // watch(sourceData, () => { + // reset(); // Uncomment if you want to reset scroll on data refresh + // }); + + return { + visibleItems, + targetRef, + loadMore, + reset, + hasMore + }; +} diff --git a/pages/AdminKlinikRuang/index.vue b/pages/AdminKlinikRuang/index.vue index e0eec8c..f85e066 100644 --- a/pages/AdminKlinikRuang/index.vue +++ b/pages/AdminKlinikRuang/index.vue @@ -104,7 +104,8 @@ const ruangStore = useRuangStore(); const loading = ref(false); const klinikRuangList = computed(() => { - return masterStore.ruangData || []; + const list = masterStore.ruangData || []; + return [...list].sort((a, b) => a.namaKlinik.localeCompare(b.namaKlinik)); }); const navigateToKlinik = (kodeKlinik, jenisLayanan) => { diff --git a/pages/Anjungan/AntrianKlinikRuang/index.vue b/pages/Anjungan/AntrianKlinikRuang/index.vue index 3e3b814..e5faa73 100644 --- a/pages/Anjungan/AntrianKlinikRuang/index.vue +++ b/pages/Anjungan/AntrianKlinikRuang/index.vue @@ -19,9 +19,9 @@
-
+
+ + +
@@ -96,11 +99,7 @@
- +
@@ -110,6 +109,7 @@ import { ref, computed, onMounted } from 'vue'; import { useMasterStore } from '@/stores/masterStore'; import { useClinicStore } from '@/stores/clinicStore'; import { useRuangStore } from '@/stores/ruangStore'; +import { useInfiniteScroll } from '@/composables/useInfiniteScroll'; import { useRoute } from '#app'; definePageMeta({ @@ -123,30 +123,15 @@ const route = useRoute(); // Simplified computed to use masterStore.ruangData (which is already grouped/processed by ruangStore) const kliniksWithRuang = computed(() => { - return masterStore.ruangData || []; + const list = masterStore.ruangData || []; + return [...list].sort((a, b) => a.namaKlinik.localeCompare(b.namaKlinik)); }); -// Pagination -const itemsPerPage = 12; // Increased for better fill -const page = computed({ - get: () => Number(route.query.page || 1), - set: (val) => navigateTo({ query: { ...route.query, page: val } }), -}); +// Infinite Scroll +const { visibleItems: visibleKliniks, targetRef, hasMore } = useInfiniteScroll(kliniksWithRuang, 12); -const totalPages = computed(() => Math.max(1, Math.ceil(kliniksWithRuang.value.length / itemsPerPage))); +// Deprecated: Pagination logic removed -const paginatedKliniks = computed(() => { - const start = (page.value - 1) * itemsPerPage; - return kliniksWithRuang.value.slice(start, start + itemsPerPage); -}); - -const goPrev = () => { - if (page.value > 1) page.value = page.value - 1; -}; - -const goNext = () => { - if (page.value < totalPages.value) page.value = page.value + 1; -}; const navigateToKlinik = (klinik) => { // Build URL with jenisLayanan query parameter to differentiate same clinic codes diff --git a/pages/Anjungan/AntrianLoket/index.vue b/pages/Anjungan/AntrianLoket/index.vue index ab9ac8d..c7a54b8 100644 --- a/pages/Anjungan/AntrianLoket/index.vue +++ b/pages/Anjungan/AntrianLoket/index.vue @@ -18,80 +18,86 @@ -
-
-
-
-
-

{{ loket.namaLoket }}

+ +
+
+
+
+
+

{{ loket.namaLoket }}

+
-
- -
-
- mdi-hospital-building - {{ loket.pelayanan?.length || 0 }} Pelayanan + +
+
+ mdi-hospital-building + {{ loket.pelayanan?.length || 0 }} Pelayanan +
+
+ + {{ getKlinikName(pelayananKode, loket) }} + + + +{{ (loket.pelayanan || []).length - 3 }} + +
-
- + - {{ getKlinikName(pelayananKode, loket) }} - - - +{{ (loket.pelayanan || []).length - 3 }} - + mdi-eye + Tampilkan +
- + +
+ mdi-view-dashboard-off +

Tidak Ada Loket Tersedia

+

Silakan tambah loket terlebih dahulu di halaman master

+ + mdi-cog + Ke Halaman Master + +
+ +
+ +
@@ -99,6 +105,7 @@ import { computed, onMounted } from 'vue'; import { useLoketStore } from '@/stores/loketStore'; import { useMasterStore } from '@/stores/masterStore'; +import { useInfiniteScroll } from '@/composables/useInfiniteScroll'; import { useRoute } from '#app'; definePageMeta({ @@ -138,27 +145,12 @@ const allLokets = computed(() => { return Array.isArray(lokets) ? lokets : (lokets.value || []) }); -// Pagination -const itemsPerPage = 20; -const page = computed({ - get: () => Number(route.query.page || 1), - set: (val) => navigateTo({ query: { ...route.query, page: val } }), -}); +// Infinite Scroll (Replaces Pagination) +const pageSize = 20; +const { visibleItems: visibleLokets, targetRef, hasMore } = useInfiniteScroll(allLokets, pageSize); -const totalPages = computed(() => Math.max(1, Math.ceil(allLokets.value.length / itemsPerPage))); +// Deprecated: Pagination logic removed -const paginatedLokets = computed(() => { - const start = (page.value - 1) * itemsPerPage; - return allLokets.value.slice(start, start + itemsPerPage); -}); - -const goPrev = () => { - if (page.value > 1) page.value = page.value - 1; -}; - -const goNext = () => { - if (page.value < totalPages.value) page.value = page.value + 1; -}; const navigateToLoket = (loketId) => { navigateTo(`/anjungan/antrianloket/${loketId}`); diff --git a/pages/verifikasiAkun/VerifikasiAkun.vue b/pages/verifikasiAkun/VerifikasiAkun.vue index fc2323e..5f08148 100644 --- a/pages/verifikasiAkun/VerifikasiAkun.vue +++ b/pages/verifikasiAkun/VerifikasiAkun.vue @@ -134,38 +134,39 @@