wip: select regency paginated
todo: search reactive wip: paginated regency todo: search bind wip gess
This commit is contained in:
@@ -10,6 +10,7 @@ import RadioDisability from './_common/radio-disability.vue'
|
|||||||
import RadioGender from './_common/radio-gender.vue'
|
import RadioGender from './_common/radio-gender.vue'
|
||||||
import RadioNationality from './_common/radio-nationality.vue'
|
import RadioNationality from './_common/radio-nationality.vue'
|
||||||
import RadioNewborn from './_common/radio-newborn.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 SelectDisability from './_common/select-disability.vue'
|
||||||
import SelectDob from './_common/select-dob.vue'
|
import SelectDob from './_common/select-dob.vue'
|
||||||
import SelectEducation from './_common/select-education.vue'
|
import SelectEducation from './_common/select-education.vue'
|
||||||
@@ -66,12 +67,19 @@ defineExpose({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-1 md:grid-cols-3">
|
<div class="grid grid-cols-1 md:grid-cols-3">
|
||||||
<InputBase
|
<!-- <InputBase
|
||||||
field-name="birthPlace"
|
field-name="birthPlace"
|
||||||
label="Tempat Lahir"
|
label="Tempat Lahir"
|
||||||
placeholder="Malang"
|
placeholder="Malang"
|
||||||
:errors="errors"
|
:errors="errors"
|
||||||
is-required
|
is-required
|
||||||
|
/> -->
|
||||||
|
<SelectBirthPlace
|
||||||
|
field-name="birthPlace"
|
||||||
|
label="Tempat Lahir"
|
||||||
|
placeholder="Pilih tempat lahir"
|
||||||
|
:errors="errors"
|
||||||
|
is-required
|
||||||
/>
|
/>
|
||||||
<SelectDob
|
<SelectDob
|
||||||
label="Tanggal Lahir"
|
label="Tanggal Lahir"
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<FieldGroup :class="cn('select-field-group', fieldGroupClass, containerClass)">
|
||||||
|
<Label :label-for="fieldName" :is-required="isRequired">
|
||||||
|
Tempat Lahir
|
||||||
|
</Label>
|
||||||
|
<Field :id="fieldName" :errors="errors" :class="cn('select-field-wrapper')">
|
||||||
|
<FormField v-slot="{ componentField }" :name="fieldName">
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<ComboboxPaginated :id="fieldName" v-bind="componentField" :items="regencyOptions"
|
||||||
|
:placeholder="dynamicPlaceholder" :is-disabled="isFieldDisabled" search-placeholder="Cari tempat lahir..."
|
||||||
|
empty-message="Tempat lahir tidak ditemukan"
|
||||||
|
:page="paginationMeta.page"
|
||||||
|
:total-page="paginationMeta.totalPage"
|
||||||
|
:has-next="paginationMeta.hasNext"
|
||||||
|
:has-prev="paginationMeta.hasPrev"
|
||||||
|
:is-loading="isLoading"
|
||||||
|
@update:searchText="onSearchChange"
|
||||||
|
@next="nextPage()"
|
||||||
|
@prev="prevPage()"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</Field>
|
||||||
|
</FieldGroup>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,208 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { cn } from '~/lib/utils'
|
||||||
|
import { type Item } from './index'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
id?: string
|
||||||
|
modelValue?: string
|
||||||
|
items: Item[]
|
||||||
|
placeholder?: string
|
||||||
|
searchPlaceholder?: string
|
||||||
|
emptyMessage?: string
|
||||||
|
class?: string
|
||||||
|
isDisabled?: boolean
|
||||||
|
page?: number
|
||||||
|
totalPage?: number
|
||||||
|
hasNext?: boolean
|
||||||
|
hasPrev?: boolean
|
||||||
|
isLoading?: boolean
|
||||||
|
searchText?: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
'update:modelValue': [value: string]
|
||||||
|
'update:searchText': [value: string]
|
||||||
|
'page-change': [page: number]
|
||||||
|
next: []
|
||||||
|
prev: []
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const open = ref(false)
|
||||||
|
const internalSearchText = ref(props.searchText || '')
|
||||||
|
|
||||||
|
// Keep internal search text synced with props
|
||||||
|
watch(
|
||||||
|
() => props.searchText,
|
||||||
|
(val) => {
|
||||||
|
if (val !== internalSearchText.value) internalSearchText.value = val || ''
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
const selectedItem = computed(() => props.items.find((item) => item.value === props.modelValue))
|
||||||
|
const displayText = computed(() => {
|
||||||
|
if (selectedItem.value?.label) return selectedItem.value.label
|
||||||
|
return props.placeholder || 'Pilih item'
|
||||||
|
})
|
||||||
|
|
||||||
|
const searchableItems = computed(() => {
|
||||||
|
const itemsWithSearch = props.items.map((item) => ({
|
||||||
|
...item,
|
||||||
|
searchValue: `${item.code || ''} ${item.label}`.trim(),
|
||||||
|
isSelected: item.value === props.modelValue,
|
||||||
|
}))
|
||||||
|
|
||||||
|
return itemsWithSearch.sort((a, b) => {
|
||||||
|
const aPriority = a.priority ?? 0
|
||||||
|
const bPriority = b.priority ?? 0
|
||||||
|
if (aPriority !== bPriority) return bPriority - aPriority
|
||||||
|
if (a.isSelected && !b.isSelected) return -1
|
||||||
|
if (!a.isSelected && b.isSelected) return 1
|
||||||
|
return a.label.localeCompare(b.label)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
function onSelect(item: Item) {
|
||||||
|
emit('update:modelValue', item.value)
|
||||||
|
open.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSearchInput(value: string) {
|
||||||
|
console.log('[ComboboxPaginated] emit update:searchText', value)
|
||||||
|
internalSearchText.value = value
|
||||||
|
emit('update:searchText', value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePrev() {
|
||||||
|
if (props.hasPrev) emit('prev')
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleNext() {
|
||||||
|
if (props.hasNext) emit('next')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Popover v-model:open="open">
|
||||||
|
<PopoverTrigger as-child>
|
||||||
|
<Button
|
||||||
|
:id="props.id"
|
||||||
|
:disabled="props.isDisabled"
|
||||||
|
variant="outline"
|
||||||
|
role="combobox"
|
||||||
|
:aria-expanded="open"
|
||||||
|
:aria-controls="`${props.id}-list`"
|
||||||
|
:aria-describedby="`${props.id}-search`"
|
||||||
|
:class="
|
||||||
|
cn(
|
||||||
|
'h-8 w-full justify-between rounded-md border px-3 font-normal focus:outline-none focus:ring-1 focus:ring-black dark:!border-slate-400 dark:focus:ring-white md:text-xs 2xl:h-9 2xl:text-sm',
|
||||||
|
{
|
||||||
|
'cursor-not-allowed border-gray-300 bg-gray-100 text-gray-500 opacity-50': props.isDisabled,
|
||||||
|
'border-gray-400 bg-white text-black hover:bg-gray-50 dark:border-gray-600 dark:bg-slate-950 dark:text-white dark:hover:bg-gray-700':
|
||||||
|
!props.isDisabled,
|
||||||
|
'text-gray-400 dark:text-gray-500': !props.modelValue && !props.isDisabled,
|
||||||
|
},
|
||||||
|
props.class,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ displayText }}
|
||||||
|
<Icon
|
||||||
|
name="i-lucide-chevrons-up-down"
|
||||||
|
:class="
|
||||||
|
cn('ml-2 h-4 w-4 shrink-0', {
|
||||||
|
'opacity-30': props.isDisabled,
|
||||||
|
'text-gray-500 opacity-50 dark:text-gray-300': !props.isDisabled,
|
||||||
|
})
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
|
||||||
|
<PopoverContent
|
||||||
|
class="w-[var(--radix-popover-trigger-width)] border border-gray-200 bg-white p-0 dark:border-gray-700 dark:bg-gray-800"
|
||||||
|
>
|
||||||
|
<Command class="bg-white dark:bg-gray-800">
|
||||||
|
<CommandInput
|
||||||
|
:id="`${props.id}-search`"
|
||||||
|
class="h-9 border-0 border-b border-gray-200 bg-white text-black focus:ring-0 dark:border-gray-700 dark:bg-gray-800 dark:text-white"
|
||||||
|
:placeholder="searchPlaceholder || 'Cari...'"
|
||||||
|
v-model="internalSearchText"
|
||||||
|
@input="onSearchInput(($event.target as HTMLInputElement).value)"
|
||||||
|
:aria-label="`Cari ${displayText}`"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<CommandEmpty class="py-6 text-center text-sm text-gray-500 dark:text-gray-400">
|
||||||
|
{{ emptyMessage || 'Item tidak ditemukan.' }}
|
||||||
|
</CommandEmpty>
|
||||||
|
|
||||||
|
<CommandList
|
||||||
|
:id="`${props.id}-list`"
|
||||||
|
role="listbox"
|
||||||
|
class="max-h-60 overflow-auto"
|
||||||
|
>
|
||||||
|
<CommandGroup>
|
||||||
|
<CommandItem
|
||||||
|
v-for="item in searchableItems"
|
||||||
|
:key="item.value"
|
||||||
|
:value="item.value"
|
||||||
|
:class="
|
||||||
|
cn(
|
||||||
|
'flex w-full cursor-pointer items-center justify-between rounded-sm px-2 py-1.5 md:text-xs xl:text-sm',
|
||||||
|
'text-black focus:outline-none dark:text-white',
|
||||||
|
'hover:bg-primary hover:text-white focus:bg-primary focus:text-white',
|
||||||
|
'data-[highlighted]:bg-primary data-[highlighted]:text-white',
|
||||||
|
)
|
||||||
|
"
|
||||||
|
@select="onSelect(item)"
|
||||||
|
>
|
||||||
|
<div class="flex w-full items-center justify-between">
|
||||||
|
<span>{{ item.label }}</span>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span
|
||||||
|
v-if="item.code"
|
||||||
|
class="text-xs text-muted-foreground"
|
||||||
|
>
|
||||||
|
{{ item.code }}
|
||||||
|
</span>
|
||||||
|
<Icon
|
||||||
|
name="i-lucide-check"
|
||||||
|
:class="cn('h-4 w-4', props.modelValue === item.value ? 'opacity-100' : 'opacity-0')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CommandItem>
|
||||||
|
</CommandGroup>
|
||||||
|
</CommandList>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-between border-t border-gray-200 p-2 text-xs dark:border-gray-700">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<Icon
|
||||||
|
v-if="props.isLoading"
|
||||||
|
name="i-lucide-loader-2"
|
||||||
|
class="h-3 w-3 animate-spin"
|
||||||
|
/>
|
||||||
|
<span v-else>{{ props.page || 1 }} / {{ props.totalPage || 1 }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="ghost"
|
||||||
|
:disabled="!props.hasPrev || props.isDisabled"
|
||||||
|
@click="handlePrev"
|
||||||
|
>
|
||||||
|
Prev
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="ghost"
|
||||||
|
:disabled="!props.hasNext || props.isDisabled"
|
||||||
|
@click="handleNext"
|
||||||
|
>
|
||||||
|
Next
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Command>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</template>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ref, computed, watch } from 'vue'
|
import { ref, computed, watch } from 'vue'
|
||||||
import { refDebounced } from '@vueuse/core'
|
import { useDebounceFn } from '@vueuse/core'
|
||||||
import type { Regency } from '~/models/regency'
|
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 type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
|
||||||
import { toTitleCase } from '~/lib/utils'
|
import { toTitleCase } from '~/lib/utils'
|
||||||
import * as regencyService from '~/services/regency.service'
|
import * as regencyService from '~/services/regency.service'
|
||||||
@@ -23,9 +23,9 @@ interface CachedRegencyData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Global cache untuk regencies berdasarkan query key (province-code + search + pagination)
|
// Global cache untuk regencies berdasarkan query key (province-code + search + pagination)
|
||||||
const regenciesCache = ref<Map<string, CachedRegencyData>>(new Map())
|
const regenciesCache = reactive(new Map<string, CachedRegencyData>())
|
||||||
const loadingStates = ref<Map<string, boolean>>(new Map())
|
const loadingStates = reactive(new Map<string, boolean>())
|
||||||
const errorStates = ref<Map<string, string | null>>(new Map())
|
const errorStates = reactive(new Map<string, string | null>())
|
||||||
|
|
||||||
interface UseRegenciesOptions {
|
interface UseRegenciesOptions {
|
||||||
provinceCode?: Ref<string | undefined> | string | undefined
|
provinceCode?: Ref<string | undefined> | string | undefined
|
||||||
@@ -36,18 +36,14 @@ interface UseRegenciesOptions {
|
|||||||
|
|
||||||
export function useRegencies(options: UseRegenciesOptions | Ref<string | undefined> | string | undefined = {}) {
|
export function useRegencies(options: UseRegenciesOptions | Ref<string | undefined> | string | undefined = {}) {
|
||||||
// Backward compatibility - jika parameter pertama adalah provinceCode
|
// Backward compatibility - jika parameter pertama adalah provinceCode
|
||||||
const normalizedOptions: UseRegenciesOptions = typeof options === 'object' && 'value' in options
|
const normalizedOptions: UseRegenciesOptions =
|
||||||
? { provinceCode: options }
|
typeof options === 'object' && 'value' in options
|
||||||
: typeof options === 'string' || options === undefined
|
? { provinceCode: options }
|
||||||
? { provinceCode: options }
|
: typeof options === 'string' || options === undefined
|
||||||
: options
|
? { provinceCode: options }
|
||||||
|
: options
|
||||||
|
|
||||||
const {
|
const { provinceCode, pageSize = 10, enablePagination = true, enableSearch = true } = normalizedOptions
|
||||||
provinceCode,
|
|
||||||
pageSize = 10,
|
|
||||||
enablePagination = true,
|
|
||||||
enableSearch = true
|
|
||||||
} = normalizedOptions
|
|
||||||
|
|
||||||
// Convert provinceCode ke ref jika bukan ref
|
// Convert provinceCode ke ref jika bukan ref
|
||||||
const provinceCodeRef =
|
const provinceCodeRef =
|
||||||
@@ -57,7 +53,6 @@ export function useRegencies(options: UseRegenciesOptions | Ref<string | undefin
|
|||||||
const currentPage = ref(1)
|
const currentPage = ref(1)
|
||||||
const currentPageSize = ref(pageSize)
|
const currentPageSize = ref(pageSize)
|
||||||
const searchQuery = ref('')
|
const searchQuery = ref('')
|
||||||
const debouncedSearch = refDebounced(searchQuery, 300)
|
|
||||||
|
|
||||||
// Function untuk generate query key
|
// Function untuk generate query key
|
||||||
const generateQueryKey = (params: RegencyQueryParams) => {
|
const generateQueryKey = (params: RegencyQueryParams) => {
|
||||||
@@ -66,7 +61,7 @@ export function useRegencies(options: UseRegenciesOptions | Ref<string | undefin
|
|||||||
params.search || '',
|
params.search || '',
|
||||||
params['page-number'] || 1,
|
params['page-number'] || 1,
|
||||||
params['page-size'] || pageSize,
|
params['page-size'] || pageSize,
|
||||||
params.sort || 'name:asc'
|
params.sort || 'name:asc',
|
||||||
]
|
]
|
||||||
return keyParts.join('|')
|
return keyParts.join('|')
|
||||||
}
|
}
|
||||||
@@ -75,48 +70,50 @@ export function useRegencies(options: UseRegenciesOptions | Ref<string | undefin
|
|||||||
const currentQueryKey = computed(() => {
|
const currentQueryKey = computed(() => {
|
||||||
return generateQueryKey({
|
return generateQueryKey({
|
||||||
'province-code': provinceCodeRef.value,
|
'province-code': provinceCodeRef.value,
|
||||||
search: enableSearch ? debouncedSearch.value : undefined,
|
search: enableSearch ? searchQuery.value : undefined,
|
||||||
'page-number': enablePagination ? currentPage.value : undefined,
|
'page-number': enablePagination ? currentPage.value : undefined,
|
||||||
'page-size': enablePagination ? currentPageSize.value : undefined,
|
'page-size': enablePagination ? currentPageSize.value : undefined,
|
||||||
sort: 'name:asc'
|
sort: 'name:asc',
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// Computed untuk mendapatkan regencies berdasarkan current query
|
// Computed untuk mendapatkan regencies berdasarkan current query
|
||||||
const regencies = computed(() => {
|
const regencies = computed(() => {
|
||||||
const queryKey = currentQueryKey.value
|
const queryKey = currentQueryKey.value
|
||||||
const cachedData = regenciesCache.value.get(queryKey)
|
const cachedData = regenciesCache.get(queryKey)
|
||||||
return cachedData?.data || []
|
return cachedData?.data || []
|
||||||
})
|
})
|
||||||
|
|
||||||
// Computed untuk pagination meta
|
// Computed untuk pagination meta
|
||||||
const paginationMeta = computed(() => {
|
const paginationMeta = computed(() => {
|
||||||
const queryKey = currentQueryKey.value
|
const queryKey = currentQueryKey.value
|
||||||
const cachedData = regenciesCache.value.get(queryKey)
|
const cachedData = regenciesCache.get(queryKey)
|
||||||
return cachedData?.meta || {
|
return (
|
||||||
recordCount: 0,
|
cachedData?.meta || {
|
||||||
page: currentPage.value,
|
recordCount: 0,
|
||||||
pageSize: currentPageSize.value,
|
page: currentPage.value,
|
||||||
totalPage: 0,
|
pageSize: currentPageSize.value,
|
||||||
hasNext: false,
|
totalPage: 0,
|
||||||
hasPrev: false,
|
hasNext: false,
|
||||||
}
|
hasPrev: false,
|
||||||
|
}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Computed untuk loading state
|
// Computed untuk loading state
|
||||||
const isLoading = computed(() => {
|
const isLoading = computed(() => {
|
||||||
const queryKey = currentQueryKey.value
|
const queryKey = currentQueryKey.value
|
||||||
return loadingStates.value.get(queryKey) || false
|
return loadingStates.get(queryKey) || false
|
||||||
})
|
})
|
||||||
|
|
||||||
// Computed untuk error state
|
// Computed untuk error state
|
||||||
const error = computed(() => {
|
const error = computed(() => {
|
||||||
const queryKey = currentQueryKey.value
|
const queryKey = currentQueryKey.value
|
||||||
return errorStates.value.get(queryKey) || null
|
return errorStates.get(queryKey) || null
|
||||||
})
|
})
|
||||||
|
|
||||||
// Computed untuk format SelectItem
|
// Computed untuk format Item
|
||||||
const regencyOptions = computed<SelectItem[]>(() => {
|
const regencyOptions = computed<Item[]>(() => {
|
||||||
return regencies.value.map((regency) => ({
|
return regencies.value.map((regency) => ({
|
||||||
label: toTitleCase(regency.name),
|
label: toTitleCase(regency.name),
|
||||||
value: regency.code,
|
value: regency.code,
|
||||||
@@ -128,29 +125,29 @@ export function useRegencies(options: UseRegenciesOptions | Ref<string | undefin
|
|||||||
async function fetchRegencies(params?: Partial<RegencyQueryParams>, forceRefresh = false) {
|
async function fetchRegencies(params?: Partial<RegencyQueryParams>, forceRefresh = false) {
|
||||||
const queryParams: RegencyQueryParams = {
|
const queryParams: RegencyQueryParams = {
|
||||||
'province-code': params?.['province-code'] || provinceCodeRef.value,
|
'province-code': params?.['province-code'] || provinceCodeRef.value,
|
||||||
search: enableSearch ? (params?.search || debouncedSearch.value) : undefined,
|
search: enableSearch ? params?.search || searchQuery.value : undefined,
|
||||||
'page-number': enablePagination ? (params?.['page-number'] || currentPage.value) : undefined,
|
'page-number': enablePagination ? params?.['page-number'] || currentPage.value : undefined,
|
||||||
'page-size': enablePagination ? (params?.['page-size'] || currentPageSize.value) : undefined,
|
'page-size': enablePagination ? params?.['page-size'] || currentPageSize.value : undefined,
|
||||||
sort: params?.sort || 'name:asc'
|
sort: params?.sort || 'name:asc',
|
||||||
}
|
}
|
||||||
|
|
||||||
// Jika tidak ada province code, return
|
// Jika tidak ada province code, return
|
||||||
if (!queryParams['province-code']) return
|
// if (!queryParams['province-code']) return // buat komponen select birth
|
||||||
|
|
||||||
const queryKey = generateQueryKey(queryParams)
|
const queryKey = generateQueryKey(queryParams)
|
||||||
|
|
||||||
// Jika tidak force refresh dan sudah ada cache, skip
|
// Jika tidak force refresh dan sudah ada cache, skip
|
||||||
if (!forceRefresh && regenciesCache.value.has(queryKey)) {
|
if (!forceRefresh && regenciesCache.has(queryKey)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Jika sedang loading, skip untuk mencegah duplicate calls
|
// Jika sedang loading, skip untuk mencegah duplicate calls
|
||||||
if (loadingStates.value.get(queryKey)) {
|
if (loadingStates.get(queryKey)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
loadingStates.value.set(queryKey, true)
|
loadingStates.set(queryKey, true)
|
||||||
errorStates.value.set(queryKey, null)
|
errorStates.set(queryKey, null)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Prepare API parameters
|
// Prepare API parameters
|
||||||
@@ -183,9 +180,10 @@ export function useRegencies(options: UseRegenciesOptions | Ref<string | undefin
|
|||||||
recordCount: meta.record_totalCount,
|
recordCount: meta.record_totalCount,
|
||||||
page: queryParams['page-number'] || 1,
|
page: queryParams['page-number'] || 1,
|
||||||
pageSize: queryParams['page-size'] || regenciesData.length,
|
pageSize: queryParams['page-size'] || regenciesData.length,
|
||||||
totalPage: enablePagination && queryParams['page-size']
|
totalPage:
|
||||||
? Math.ceil(meta.record_totalCount / queryParams['page-size'])
|
enablePagination && queryParams['page-size']
|
||||||
: 1,
|
? Math.ceil(meta.record_totalCount / queryParams['page-size'])
|
||||||
|
: 1,
|
||||||
hasNext: false,
|
hasNext: false,
|
||||||
hasPrev: false,
|
hasPrev: false,
|
||||||
}
|
}
|
||||||
@@ -196,27 +194,27 @@ export function useRegencies(options: UseRegenciesOptions | Ref<string | undefin
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Cache the result
|
// Cache the result
|
||||||
regenciesCache.value.set(queryKey, {
|
regenciesCache.set(queryKey, {
|
||||||
data: regenciesData,
|
data: [...regenciesData],
|
||||||
meta: paginationMeta,
|
meta: { ...paginationMeta },
|
||||||
queryKey
|
queryKey,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
errorStates.value.set(queryKey, 'Gagal memuat data kabupaten/kota')
|
errorStates.set(queryKey, 'Gagal memuat data kabupaten/kota')
|
||||||
console.error('Failed to fetch regencies:', response)
|
console.error('Failed to fetch regencies:', response)
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
errorStates.value.set(queryKey, 'Terjadi kesalahan saat memuat data kabupaten/kota')
|
errorStates.set(queryKey, 'Terjadi kesalahan saat memuat data kabupaten/kota')
|
||||||
console.error('Error fetching regencies:', err)
|
console.error('Error fetching regencies:', err)
|
||||||
} finally {
|
} finally {
|
||||||
loadingStates.value.set(queryKey, false)
|
loadingStates.set(queryKey, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function untuk mencari regency berdasarkan code (search di semua cached data)
|
// Function untuk mencari regency berdasarkan code (search di semua cached data)
|
||||||
function getRegencyByCode(code: string): Regency | undefined {
|
function getRegencyByCode(code: string): Regency | undefined {
|
||||||
// Search di semua cached data
|
// Search di semua cached data
|
||||||
for (const cachedData of regenciesCache.value.values()) {
|
for (const cachedData of regenciesCache.values()) {
|
||||||
const found = cachedData.data.find((regency) => regency.code === code)
|
const found = cachedData.data.find((regency) => regency.code === code)
|
||||||
if (found) return found
|
if (found) return found
|
||||||
}
|
}
|
||||||
@@ -226,7 +224,7 @@ export function useRegencies(options: UseRegenciesOptions | Ref<string | undefin
|
|||||||
// Function untuk mencari regency berdasarkan name (search di semua cached data)
|
// Function untuk mencari regency berdasarkan name (search di semua cached data)
|
||||||
function getRegencyByName(name: string): Regency | undefined {
|
function getRegencyByName(name: string): Regency | undefined {
|
||||||
// Search di semua cached data
|
// Search di semua cached data
|
||||||
for (const cachedData of regenciesCache.value.values()) {
|
for (const cachedData of regenciesCache.values()) {
|
||||||
const found = cachedData.data.find((regency) => regency.name.toLowerCase() === name.toLowerCase())
|
const found = cachedData.data.find((regency) => regency.name.toLowerCase() === name.toLowerCase())
|
||||||
if (found) return found
|
if (found) return found
|
||||||
}
|
}
|
||||||
@@ -274,24 +272,24 @@ export function useRegencies(options: UseRegenciesOptions | Ref<string | undefin
|
|||||||
if (code) {
|
if (code) {
|
||||||
// Clear semua cache yang mengandung province code tersebut
|
// Clear semua cache yang mengandung province code tersebut
|
||||||
const keysToDelete: string[] = []
|
const keysToDelete: string[] = []
|
||||||
for (const [key] of regenciesCache.value.entries()) {
|
for (const [key] of regenciesCache.entries()) {
|
||||||
if (key.startsWith(code + '|')) {
|
if (key.startsWith(code + '|')) {
|
||||||
keysToDelete.push(key)
|
keysToDelete.push(key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
keysToDelete.forEach(key => {
|
keysToDelete.forEach((key) => {
|
||||||
regenciesCache.value.delete(key)
|
regenciesCache.delete(key)
|
||||||
loadingStates.value.delete(key)
|
loadingStates.delete(key)
|
||||||
errorStates.value.delete(key)
|
errorStates.delete(key)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function untuk clear semua cache
|
// Function untuk clear semua cache
|
||||||
function clearAllCache() {
|
function clearAllCache() {
|
||||||
regenciesCache.value.clear()
|
regenciesCache.clear()
|
||||||
loadingStates.value.clear()
|
loadingStates.clear()
|
||||||
errorStates.value.clear()
|
errorStates.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function untuk refresh data
|
// Function untuk refresh data
|
||||||
@@ -318,25 +316,30 @@ export function useRegencies(options: UseRegenciesOptions | Ref<string | undefin
|
|||||||
{ immediate: true },
|
{ immediate: true },
|
||||||
)
|
)
|
||||||
|
|
||||||
// Watch perubahan search query untuk auto fetch
|
const triggerFetchAfterIdle = useDebounceFn(() => {
|
||||||
watch(
|
if (enableSearch) {
|
||||||
debouncedSearch,
|
currentPage.value = 1
|
||||||
(newSearch, oldSearch) => {
|
fetchRegencies()
|
||||||
if (enableSearch && newSearch !== oldSearch) {
|
|
||||||
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 perubahan pagination untuk auto fetch
|
||||||
watch(
|
watch([currentPage, currentPageSize], () => {
|
||||||
[currentPage, currentPageSize],
|
if (enablePagination) {
|
||||||
() => {
|
fetchRegencies()
|
||||||
if (enablePagination) {
|
|
||||||
fetchRegencies()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
})
|
||||||
|
|
||||||
|
watch(regencyOptions, (val) => {
|
||||||
|
console.log('[regencyOptions] updated', val.length)
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// Data
|
// Data
|
||||||
|
|||||||
Reference in New Issue
Block a user