Files
Khafid Prayoga bc286f16c8 wip: select regency paginated
todo: search reactive

wip: paginated regency

todo: search bind

wip gess
2025-10-14 11:36:55 +07:00

387 lines
12 KiB
TypeScript

import { ref, computed, watch } from 'vue'
import { useDebounceFn } from '@vueuse/core'
import type { Regency } from '~/models/regency'
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'
// Interface untuk query parameters
interface RegencyQueryParams {
'province-code'?: string
search?: string
'page-number'?: number
'page-size'?: number
sort?: string
}
// Interface untuk cached data dengan pagination
interface CachedRegencyData {
data: Regency[]
meta: PaginationMeta
queryKey: string // untuk tracking query yang berbeda
}
// Global cache untuk regencies berdasarkan query key (province-code + search + pagination)
const regenciesCache = reactive(new Map<string, CachedRegencyData>())
const loadingStates = reactive(new Map<string, boolean>())
const errorStates = reactive(new Map<string, string | null>())
interface UseRegenciesOptions {
provinceCode?: Ref<string | undefined> | string | undefined
pageSize?: number
enablePagination?: boolean
enableSearch?: boolean
}
export function useRegencies(options: UseRegenciesOptions | Ref<string | undefined> | 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 { provinceCode, pageSize = 10, enablePagination = true, enableSearch = true } = normalizedOptions
// Convert provinceCode ke ref jika bukan ref
const provinceCodeRef =
typeof provinceCode === 'string' || provinceCode === undefined ? ref(provinceCode) : provinceCode || ref(undefined)
// State untuk pagination dan search
const currentPage = ref(1)
const currentPageSize = ref(pageSize)
const searchQuery = ref('')
// Function untuk generate query key
const generateQueryKey = (params: RegencyQueryParams) => {
const keyParts = [
params['province-code'] || '',
params.search || '',
params['page-number'] || 1,
params['page-size'] || pageSize,
params.sort || 'name:asc',
]
return keyParts.join('|')
}
// Current query key
const currentQueryKey = computed(() => {
return generateQueryKey({
'province-code': provinceCodeRef.value,
search: enableSearch ? searchQuery.value : undefined,
'page-number': enablePagination ? currentPage.value : undefined,
'page-size': enablePagination ? currentPageSize.value : undefined,
sort: 'name:asc',
})
})
// Computed untuk mendapatkan regencies berdasarkan current query
const regencies = computed(() => {
const queryKey = currentQueryKey.value
const cachedData = regenciesCache.get(queryKey)
return cachedData?.data || []
})
// Computed untuk pagination meta
const paginationMeta = computed(() => {
const queryKey = currentQueryKey.value
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.get(queryKey) || false
})
// Computed untuk error state
const error = computed(() => {
const queryKey = currentQueryKey.value
return errorStates.get(queryKey) || null
})
// Computed untuk format Item
const regencyOptions = computed<Item[]>(() => {
return regencies.value.map((regency) => ({
label: toTitleCase(regency.name),
value: regency.code,
searchValue: `${regency.code} ${regency.name}`.trim(),
}))
})
// Function untuk fetch regencies dengan pagination dan search
async function fetchRegencies(params?: Partial<RegencyQueryParams>, forceRefresh = false) {
const queryParams: RegencyQueryParams = {
'province-code': params?.['province-code'] || provinceCodeRef.value,
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 // buat komponen select birth
const queryKey = generateQueryKey(queryParams)
// Jika tidak force refresh dan sudah ada cache, skip
if (!forceRefresh && regenciesCache.has(queryKey)) {
return
}
// Jika sedang loading, skip untuk mencegah duplicate calls
if (loadingStates.get(queryKey)) {
return
}
loadingStates.set(queryKey, true)
errorStates.set(queryKey, null)
try {
// Prepare API parameters
const apiParams: any = {
sort: queryParams.sort,
'province-code': queryParams['province-code'],
}
// Add pagination or no-limit based on enablePagination
if (enablePagination && queryParams['page-number'] && queryParams['page-size']) {
apiParams['page-number'] = queryParams['page-number']
apiParams['page-size'] = queryParams['page-size']
} else {
apiParams['page-no-limit'] = true
}
// Add search if enabled and has value
if (enableSearch && queryParams.search && queryParams.search.trim().length >= 3) {
apiParams.search = queryParams.search.trim()
}
const response = await regencyService.getList(apiParams)
if (response.success) {
const regenciesData = response.body.data || []
const meta = response.body.meta || { record_totalCount: regenciesData.length }
// Create pagination meta
const paginationMeta: PaginationMeta = {
recordCount: meta.record_totalCount,
page: queryParams['page-number'] || 1,
pageSize: queryParams['page-size'] || regenciesData.length,
totalPage:
enablePagination && queryParams['page-size']
? Math.ceil(meta.record_totalCount / queryParams['page-size'])
: 1,
hasNext: false,
hasPrev: false,
}
if (enablePagination && queryParams['page-size']) {
paginationMeta.hasNext = paginationMeta.page < paginationMeta.totalPage
paginationMeta.hasPrev = paginationMeta.page > 1
}
// Cache the result
regenciesCache.set(queryKey, {
data: [...regenciesData],
meta: { ...paginationMeta },
queryKey,
})
} else {
errorStates.set(queryKey, 'Gagal memuat data kabupaten/kota')
console.error('Failed to fetch regencies:', response)
}
} catch (err) {
errorStates.set(queryKey, 'Terjadi kesalahan saat memuat data kabupaten/kota')
console.error('Error fetching regencies:', err)
} finally {
loadingStates.set(queryKey, false)
}
}
// Function untuk mencari regency berdasarkan code (search di semua cached data)
function getRegencyByCode(code: string): Regency | undefined {
// Search di semua cached data
for (const cachedData of regenciesCache.values()) {
const found = cachedData.data.find((regency) => regency.code === code)
if (found) return found
}
return undefined
}
// Function untuk mencari regency berdasarkan name (search di semua cached data)
function getRegencyByName(name: string): Regency | undefined {
// Search di semua cached data
for (const cachedData of regenciesCache.values()) {
const found = cachedData.data.find((regency) => regency.name.toLowerCase() === name.toLowerCase())
if (found) return found
}
return undefined
}
// Function untuk pagination
function goToPage(page: number) {
if (page >= 1 && page <= paginationMeta.value.totalPage) {
currentPage.value = page
}
}
function nextPage() {
if (paginationMeta.value.hasNext) {
currentPage.value += 1
}
}
function prevPage() {
if (paginationMeta.value.hasPrev) {
currentPage.value -= 1
}
}
function changePageSize(size: number) {
currentPageSize.value = size
currentPage.value = 1 // Reset ke halaman pertama
}
// Function untuk search
function setSearch(query: string) {
searchQuery.value = query
currentPage.value = 1 // Reset ke halaman pertama saat search
}
function clearSearch() {
searchQuery.value = ''
currentPage.value = 1
}
// Function untuk clear cache berdasarkan query key pattern
function clearCache(provinceCodeParam?: string) {
const code = provinceCodeParam || provinceCodeRef.value
if (code) {
// Clear semua cache yang mengandung province code tersebut
const keysToDelete: string[] = []
for (const [key] of regenciesCache.entries()) {
if (key.startsWith(code + '|')) {
keysToDelete.push(key)
}
}
keysToDelete.forEach((key) => {
regenciesCache.delete(key)
loadingStates.delete(key)
errorStates.delete(key)
})
}
}
// Function untuk clear semua cache
function clearAllCache() {
regenciesCache.clear()
loadingStates.clear()
errorStates.clear()
}
// Function untuk refresh data
function refreshRegencies(params?: Partial<RegencyQueryParams>) {
return fetchRegencies(params, true)
}
// Debounced province code untuk mencegah multiple calls
const debouncedProvinceCode = refDebounced(provinceCodeRef, 100)
// Watch perubahan province code untuk auto fetch
watch(
debouncedProvinceCode,
(newCode, oldCode) => {
if (newCode && newCode !== oldCode) {
// Reset pagination dan search saat province code berubah
currentPage.value = 1
if (enableSearch) {
searchQuery.value = ''
}
fetchRegencies()
}
},
{ immediate: true },
)
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(regencyOptions, (val) => {
console.log('[regencyOptions] updated', val.length)
})
return {
// Data
regencies: readonly(regencies),
regencyOptions,
// State
isLoading: readonly(isLoading),
error: readonly(error),
paginationMeta: readonly(paginationMeta),
// Search state
searchQuery: enableSearch ? searchQuery : readonly(ref('')),
// Pagination state
currentPage: enablePagination ? currentPage : readonly(ref(1)),
currentPageSize: enablePagination ? currentPageSize : readonly(ref(pageSize)),
// Methods
fetchRegencies,
refreshRegencies,
getRegencyByCode,
getRegencyByName,
clearCache,
clearAllCache,
// Pagination methods
goToPage: enablePagination ? goToPage : () => {},
nextPage: enablePagination ? nextPage : () => {},
prevPage: enablePagination ? prevPage : () => {},
changePageSize: enablePagination ? changePageSize : () => {},
// Search methods
setSearch: enableSearch ? setSearch : () => {},
clearSearch: enableSearch ? clearSearch : () => {},
}
}
// Export untuk direct access ke cached data (jika diperlukan)
export const useRegenciesCache = () => ({
regenciesCache: readonly(regenciesCache),
loadingStates: readonly(loadingStates),
errorStates: readonly(errorStates),
})