From bc286f16c8e07f7f89f77cceae8fc3b353d5e752 Mon Sep 17 00:00:00 2001 From: Khafid Prayoga Date: Mon, 13 Oct 2025 13:56:41 +0700 Subject: [PATCH] wip: select regency paginated todo: search reactive wip: paginated regency todo: search bind wip gess --- app/components/app/patient/entry-form.vue | 10 +- .../app/person/_common/select-birth-place.vue | 88 ++++++++ .../pub/my-ui/combobox/combobox-paginated.vue | 208 ++++++++++++++++++ app/composables/useRegencies.ts | 159 ++++++------- 4 files changed, 386 insertions(+), 79 deletions(-) create mode 100644 app/components/app/person/_common/select-birth-place.vue create mode 100644 app/components/pub/my-ui/combobox/combobox-paginated.vue diff --git a/app/components/app/patient/entry-form.vue b/app/components/app/patient/entry-form.vue index 26d5bebd..5ac49364 100644 --- a/app/components/app/patient/entry-form.vue +++ b/app/components/app/patient/entry-form.vue @@ -10,6 +10,7 @@ import RadioDisability from './_common/radio-disability.vue' import RadioGender from './_common/radio-gender.vue' import RadioNationality from './_common/radio-nationality.vue' import RadioNewborn from './_common/radio-newborn.vue' +import SelectBirthPlace from '~/components/app/person/_common/select-birth-place.vue' import SelectDisability from './_common/select-disability.vue' import SelectDob from './_common/select-dob.vue' import SelectEducation from './_common/select-education.vue' @@ -66,12 +67,19 @@ defineExpose({ />
- --> + +import type { FormErrors } from '~/types/error' +import ComboboxPaginated from '~/components/pub/my-ui/combobox/combobox-paginated.vue' +import FieldGroup from '~/components/pub/my-ui/form/field-group.vue' +import Field from '~/components/pub/my-ui/form/field.vue' +import Label from '~/components/pub/my-ui/form/label.vue' +import { cn } from '~/lib/utils' + +const props = defineProps<{ + fieldName?: string + isDisabled?: boolean + placeholder?: string + errors?: FormErrors + class?: string + selectClass?: string + fieldGroupClass?: string + isRequired?: boolean +}>() + +const { + fieldName = 'birthPlace', + placeholder = 'Pilih tempat lahir', + errors, + class: containerClass, + fieldGroupClass, +} = props + +// Gunakan composable untuk mengelola data regencies +const { + fetchRegencies, + regencyOptions, + isLoading, + error, + paginationMeta, + nextPage, + prevPage, + setSearch, +} = useRegencies({ enablePagination: true, pageSize: 10,enableSearch:true }) + +// Computed untuk menentukan placeholder berdasarkan state +const dynamicPlaceholder = computed(() => { + if (isLoading.value) return 'Memuat data tempat lahir...' + if (error.value) return 'Gagal memuat data' + return placeholder +}) + +// Computed untuk menentukan apakah field disabled +const isFieldDisabled = computed(() => { + return props.isDisabled || isLoading.value || !!error.value +}) + +onMounted(() => { + fetchRegencies() +}) + +function onSearchChange(query: string) { + setSearch(query) +} + + + diff --git a/app/components/pub/my-ui/combobox/combobox-paginated.vue b/app/components/pub/my-ui/combobox/combobox-paginated.vue new file mode 100644 index 00000000..74b69ef7 --- /dev/null +++ b/app/components/pub/my-ui/combobox/combobox-paginated.vue @@ -0,0 +1,208 @@ + + + diff --git a/app/composables/useRegencies.ts b/app/composables/useRegencies.ts index e480998d..d816a5ed 100644 --- a/app/composables/useRegencies.ts +++ b/app/composables/useRegencies.ts @@ -1,7 +1,7 @@ import { ref, computed, watch } from 'vue' -import { refDebounced } from '@vueuse/core' +import { useDebounceFn } from '@vueuse/core' import type { Regency } from '~/models/regency' -import type { SelectItem } from '~/components/pub/my-ui/form/select.vue' +import type { Item } from '~/components/pub/my-ui/combobox' import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type' import { toTitleCase } from '~/lib/utils' import * as regencyService from '~/services/regency.service' @@ -23,9 +23,9 @@ interface CachedRegencyData { } // Global cache untuk regencies berdasarkan query key (province-code + search + pagination) -const regenciesCache = ref>(new Map()) -const loadingStates = ref>(new Map()) -const errorStates = ref>(new Map()) +const regenciesCache = reactive(new Map()) +const loadingStates = reactive(new Map()) +const errorStates = reactive(new Map()) interface UseRegenciesOptions { provinceCode?: Ref | string | undefined @@ -36,18 +36,14 @@ interface UseRegenciesOptions { export function useRegencies(options: UseRegenciesOptions | Ref | string | undefined = {}) { // Backward compatibility - jika parameter pertama adalah provinceCode - const normalizedOptions: UseRegenciesOptions = typeof options === 'object' && 'value' in options - ? { provinceCode: options } - : typeof options === 'string' || options === undefined - ? { provinceCode: options } - : options + const normalizedOptions: UseRegenciesOptions = + typeof options === 'object' && 'value' in options + ? { provinceCode: options } + : typeof options === 'string' || options === undefined + ? { provinceCode: options } + : options - const { - provinceCode, - pageSize = 10, - enablePagination = true, - enableSearch = true - } = normalizedOptions + const { provinceCode, pageSize = 10, enablePagination = true, enableSearch = true } = normalizedOptions // Convert provinceCode ke ref jika bukan ref const provinceCodeRef = @@ -57,7 +53,6 @@ export function useRegencies(options: UseRegenciesOptions | Ref { @@ -66,7 +61,7 @@ export function useRegencies(options: UseRegenciesOptions | Ref { return generateQueryKey({ 'province-code': provinceCodeRef.value, - search: enableSearch ? debouncedSearch.value : undefined, + search: enableSearch ? searchQuery.value : undefined, 'page-number': enablePagination ? currentPage.value : undefined, 'page-size': enablePagination ? currentPageSize.value : undefined, - sort: 'name:asc' + sort: 'name:asc', }) }) // Computed untuk mendapatkan regencies berdasarkan current query const regencies = computed(() => { const queryKey = currentQueryKey.value - const cachedData = regenciesCache.value.get(queryKey) + const cachedData = regenciesCache.get(queryKey) return cachedData?.data || [] }) // Computed untuk pagination meta const paginationMeta = computed(() => { const queryKey = currentQueryKey.value - const cachedData = regenciesCache.value.get(queryKey) - return cachedData?.meta || { - recordCount: 0, - page: currentPage.value, - pageSize: currentPageSize.value, - totalPage: 0, - hasNext: false, - hasPrev: false, - } + const cachedData = regenciesCache.get(queryKey) + return ( + cachedData?.meta || { + recordCount: 0, + page: currentPage.value, + pageSize: currentPageSize.value, + totalPage: 0, + hasNext: false, + hasPrev: false, + } + ) }) // Computed untuk loading state const isLoading = computed(() => { const queryKey = currentQueryKey.value - return loadingStates.value.get(queryKey) || false + return loadingStates.get(queryKey) || false }) // Computed untuk error state const error = computed(() => { const queryKey = currentQueryKey.value - return errorStates.value.get(queryKey) || null + return errorStates.get(queryKey) || null }) - // Computed untuk format SelectItem - const regencyOptions = computed(() => { + // Computed untuk format Item + const regencyOptions = computed(() => { return regencies.value.map((regency) => ({ label: toTitleCase(regency.name), value: regency.code, @@ -128,29 +125,29 @@ export function useRegencies(options: UseRegenciesOptions | Ref, forceRefresh = false) { const queryParams: RegencyQueryParams = { 'province-code': params?.['province-code'] || provinceCodeRef.value, - search: enableSearch ? (params?.search || debouncedSearch.value) : undefined, - 'page-number': enablePagination ? (params?.['page-number'] || currentPage.value) : undefined, - 'page-size': enablePagination ? (params?.['page-size'] || currentPageSize.value) : undefined, - sort: params?.sort || 'name:asc' + search: enableSearch ? params?.search || searchQuery.value : undefined, + 'page-number': enablePagination ? params?.['page-number'] || currentPage.value : undefined, + 'page-size': enablePagination ? params?.['page-size'] || currentPageSize.value : undefined, + sort: params?.sort || 'name:asc', } // Jika tidak ada province code, return - if (!queryParams['province-code']) return + // if (!queryParams['province-code']) return // buat komponen select birth const queryKey = generateQueryKey(queryParams) // Jika tidak force refresh dan sudah ada cache, skip - if (!forceRefresh && regenciesCache.value.has(queryKey)) { + if (!forceRefresh && regenciesCache.has(queryKey)) { return } // Jika sedang loading, skip untuk mencegah duplicate calls - if (loadingStates.value.get(queryKey)) { + if (loadingStates.get(queryKey)) { return } - loadingStates.value.set(queryKey, true) - errorStates.value.set(queryKey, null) + loadingStates.set(queryKey, true) + errorStates.set(queryKey, null) try { // Prepare API parameters @@ -183,9 +180,10 @@ export function useRegencies(options: UseRegenciesOptions | Ref regency.code === code) if (found) return found } @@ -226,7 +224,7 @@ export function useRegencies(options: UseRegenciesOptions | Ref regency.name.toLowerCase() === name.toLowerCase()) if (found) return found } @@ -274,24 +272,24 @@ export function useRegencies(options: UseRegenciesOptions | Ref { - regenciesCache.value.delete(key) - loadingStates.value.delete(key) - errorStates.value.delete(key) + keysToDelete.forEach((key) => { + regenciesCache.delete(key) + loadingStates.delete(key) + errorStates.delete(key) }) } } // Function untuk clear semua cache function clearAllCache() { - regenciesCache.value.clear() - loadingStates.value.clear() - errorStates.value.clear() + regenciesCache.clear() + loadingStates.clear() + errorStates.clear() } // Function untuk refresh data @@ -318,25 +316,30 @@ export function useRegencies(options: UseRegenciesOptions | Ref { - if (enableSearch && newSearch !== oldSearch) { - fetchRegencies() - } + const triggerFetchAfterIdle = useDebounceFn(() => { + if (enableSearch) { + currentPage.value = 1 + fetchRegencies() } - ) + }, 1000) + + // Watch perubahan search query untuk auto fetch dan reset halaman + watch(searchQuery, (newSearch, oldSearch) => { + if (newSearch !== oldSearch) { + triggerFetchAfterIdle() + } + }) // Watch perubahan pagination untuk auto fetch - watch( - [currentPage, currentPageSize], - () => { - if (enablePagination) { - fetchRegencies() - } + watch([currentPage, currentPageSize], () => { + if (enablePagination) { + fetchRegencies() } - ) + }) + + watch(regencyOptions, (val) => { + console.log('[regencyOptions] updated', val.length) + }) return { // Data