Files
simrsx-fe/app/composables/useRegencies.ts
Khafid Prayoga 55239606af feat(patient): address integration to backend apis
feat(patient): add newborn status field and validation

- Add radio button component for newborn status selection
- Update patient schema with newborn status validation
- Remove deprecated alias field from person model
- Refactor disability type handling in patient schema

fix(patient): correct address comparison logic and schema

Update the patient address comparison to use boolean instead of string '1' and modify the schema to transform the string value to boolean. This ensures consistent type usage throughout the application.

feat(models): add village and district model interfaces

Add new model interfaces for Village and District with their respective generator functions. These models will be used to handle administrative division data in the application.

feat(address): implement dynamic province selection with caching

- Add province service for CRUD operations
- Create useProvinces composable with caching and loading states
- Update select-province component to use dynamic data
- Export SelectItem interface for type consistency
- Improve combobox styling and accessibility

feat(address-form): implement dynamic regency selection with caching

- Add new regency service for CRUD operations
- Create useRegencies composable with caching and loading states
- Update SelectRegency component to use dynamic data based on province selection
- Improve placeholder and disabled state handling

feat(address-form): implement dynamic district selection

- Add district service for CRUD operations
- Create useDistricts composable with caching and loading states
- Update SelectDistrict component to use dynamic data
- Remove hardcoded district options and implement regency-based filtering

feat(address-form): implement dynamic village selection with caching

- Add village service for CRUD operations
- Create useVillages composable with caching and loading states
- Update SelectVillage component to fetch villages based on district
- Remove hardcoded village options in favor of API-driven data

feat(address-form): improve address selection with debouncing and request deduplication

- Add debouncing to prevent rapid API calls when selecting addresses
- Implement request deduplication to avoid duplicate API calls
- Add delayed form reset to ensure proper composable cleanup
- Add isUserAction flag to force refresh when user changes selection
2025-10-08 15:58:22 +07:00

182 lines
5.7 KiB
TypeScript

import { ref, computed, watch } from 'vue'
import { refDebounced } from '@vueuse/core'
import type { Regency } from '~/models/regency'
import type { SelectItem } from '~/components/pub/my-ui/form/select.vue'
import { toTitleCase } from '~/lib/utils'
import * as regencyService from '~/services/regency.service'
// Global cache untuk regencies berdasarkan province code
const regenciesCache = ref<Map<string, Regency[]>>(new Map())
const loadingStates = ref<Map<string, boolean>>(new Map())
const errorStates = ref<Map<string, string | null>>(new Map())
export function useRegencies(provinceCode: Ref<string | undefined> | string | undefined) {
// Convert provinceCode ke ref jika bukan ref
const provinceCodeRef =
typeof provinceCode === 'string' || provinceCode === undefined ? ref(provinceCode) : provinceCode
// Computed untuk mendapatkan regencies berdasarkan province code
const regencies = computed(() => {
const code = provinceCodeRef.value
if (!code) return []
return regenciesCache.value.get(code) || []
})
// Computed untuk loading state
const isLoading = computed(() => {
const code = provinceCodeRef.value
if (!code) return false
return loadingStates.value.get(code) || false
})
// Computed untuk error state
const error = computed(() => {
const code = provinceCodeRef.value
if (!code) return null
return errorStates.value.get(code) || null
})
// Computed untuk format SelectItem
const regencyOptions = computed<SelectItem[]>(() => {
return regencies.value.map((regency) => ({
label: toTitleCase(regency.name),
value: regency.code,
searchValue: `${regency.code} ${regency.name}`.trim(),
}))
})
// Function untuk fetch regencies berdasarkan province code
async function fetchRegencies(provinceCodeParam?: string, forceRefresh = false, isUserAction = false) {
const code = provinceCodeParam || provinceCodeRef.value
if (!code) return
// Jika user action atau force refresh, selalu fetch
// Jika bukan user action dan sudah ada cache, skip
if (!isUserAction && !forceRefresh && regenciesCache.value.has(code)) {
return
}
// Jika sedang loading, skip untuk mencegah duplicate calls
if (loadingStates.value.get(code)) {
return
}
// Tambahan: Cek apakah ada pending request untuk code yang sama
const pendingKey = `pending_${code}`
if (loadingStates.value.get(pendingKey)) {
return
}
loadingStates.value.set(pendingKey, true)
loadingStates.value.set(code, true)
errorStates.value.set(code, null)
try {
const response = await regencyService.getList({
'page-size': '50',
sort: 'name:asc',
province_code: code,
})
if (response.success) {
const regenciesData = response.body.data || []
regenciesCache.value.set(code, regenciesData)
} else {
errorStates.value.set(code, 'Gagal memuat data kabupaten/kota')
console.error('Failed to fetch regencies:', response)
}
} catch (err) {
errorStates.value.set(code, 'Terjadi kesalahan saat memuat data kabupaten/kota')
console.error('Error fetching regencies:', err)
} finally {
loadingStates.value.set(code, false)
loadingStates.value.delete(pendingKey)
}
}
// Function untuk mencari regency berdasarkan code
function getRegencyByCode(code: string): Regency | undefined {
const provinceCode = provinceCodeRef.value
if (!provinceCode) return undefined
const regenciesForProvince = regenciesCache.value.get(provinceCode) || []
return regenciesForProvince.find((regency) => regency.code === code)
}
// Function untuk mencari regency berdasarkan name
function getRegencyByName(name: string): Regency | undefined {
const provinceCode = provinceCodeRef.value
if (!provinceCode) return undefined
const regenciesForProvince = regenciesCache.value.get(provinceCode) || []
return regenciesForProvince.find((regency) => regency.name.toLowerCase() === name.toLowerCase())
}
// Function untuk clear cache province tertentu
function clearCache(provinceCodeParam?: string) {
const code = provinceCodeParam || provinceCodeRef.value
if (code) {
regenciesCache.value.delete(code)
loadingStates.value.delete(code)
errorStates.value.delete(code)
}
}
// Function untuk clear semua cache
function clearAllCache() {
regenciesCache.value.clear()
loadingStates.value.clear()
errorStates.value.clear()
}
// Function untuk refresh data
function refreshRegencies(provinceCodeParam?: string) {
const code = provinceCodeParam || provinceCodeRef.value
if (code) {
return fetchRegencies(code, 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) {
// Jika ada oldCode berarti user action (ganti pilihan)
const isUserAction = !!oldCode
fetchRegencies(newCode, false, isUserAction)
}
},
{ immediate: true },
)
return {
// Data
regencies: readonly(regencies),
regencyOptions,
// State
isLoading: readonly(isLoading),
error: readonly(error),
// Methods
fetchRegencies,
refreshRegencies,
getRegencyByCode,
getRegencyByName,
clearCache,
clearAllCache,
}
}
// Export untuk direct access ke cached data (jika diperlukan)
export const useRegenciesCache = () => ({
regenciesCache: readonly(regenciesCache),
loadingStates: readonly(loadingStates),
errorStates: readonly(errorStates),
})