55239606af
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
181 lines
5.6 KiB
TypeScript
181 lines
5.6 KiB
TypeScript
import { ref, computed, watch } from 'vue'
|
|
import { refDebounced } from '@vueuse/core'
|
|
import type { District } from '~/models/district'
|
|
import type { SelectItem } from '~/components/pub/my-ui/form/select.vue'
|
|
import { toTitleCase } from '~/lib/utils'
|
|
import * as districtService from '~/services/district.service'
|
|
|
|
// Global cache untuk districts berdasarkan regency code
|
|
const districtsCache = ref<Map<string, District[]>>(new Map())
|
|
const loadingStates = ref<Map<string, boolean>>(new Map())
|
|
const errorStates = ref<Map<string, string | null>>(new Map())
|
|
|
|
export function useDistricts(regencyCode: Ref<string | undefined> | string | undefined) {
|
|
// Convert regencyCode ke ref jika bukan ref
|
|
const regencyCodeRef = typeof regencyCode === 'string' || regencyCode === undefined ? ref(regencyCode) : regencyCode
|
|
|
|
// Computed untuk mendapatkan districts berdasarkan regency code
|
|
const districts = computed(() => {
|
|
const code = regencyCodeRef.value
|
|
if (!code) return []
|
|
return districtsCache.value.get(code) || []
|
|
})
|
|
|
|
// Computed untuk loading state
|
|
const isLoading = computed(() => {
|
|
const code = regencyCodeRef.value
|
|
if (!code) return false
|
|
return loadingStates.value.get(code) || false
|
|
})
|
|
|
|
// Computed untuk error state
|
|
const error = computed(() => {
|
|
const code = regencyCodeRef.value
|
|
if (!code) return null
|
|
return errorStates.value.get(code) || null
|
|
})
|
|
|
|
// Computed untuk format SelectItem
|
|
const districtOptions = computed<SelectItem[]>(() => {
|
|
return districts.value.map((district) => ({
|
|
label: toTitleCase(district.name),
|
|
value: district.code,
|
|
searchValue: `${district.code} ${district.name}`.trim(),
|
|
}))
|
|
})
|
|
|
|
// Function untuk fetch districts berdasarkan regency code
|
|
async function fetchDistricts(regencyCodeParam?: string, forceRefresh = false, isUserAction = false) {
|
|
const code = regencyCodeParam || regencyCodeRef.value
|
|
if (!code) return
|
|
|
|
// Jika user action atau force refresh, selalu fetch
|
|
// Jika bukan user action dan sudah ada cache, skip
|
|
if (!isUserAction && !forceRefresh && districtsCache.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 districtService.getList({
|
|
'page-size': '50',
|
|
sort: 'name:asc',
|
|
regency_code: code,
|
|
})
|
|
|
|
if (response.success) {
|
|
const districtsData = response.body.data || []
|
|
districtsCache.value.set(code, districtsData)
|
|
} else {
|
|
errorStates.value.set(code, 'Gagal memuat data kecamatan')
|
|
console.error('Failed to fetch districts:', response)
|
|
}
|
|
} catch (err) {
|
|
errorStates.value.set(code, 'Terjadi kesalahan saat memuat data kecamatan')
|
|
console.error('Error fetching districts:', err)
|
|
} finally {
|
|
loadingStates.value.set(code, false)
|
|
loadingStates.value.delete(pendingKey)
|
|
}
|
|
}
|
|
|
|
// Function untuk mencari district berdasarkan code
|
|
function getDistrictByCode(code: string): District | undefined {
|
|
const regencyCode = regencyCodeRef.value
|
|
if (!regencyCode) return undefined
|
|
|
|
const districtsForRegency = districtsCache.value.get(regencyCode) || []
|
|
return districtsForRegency.find((district) => district.code === code)
|
|
}
|
|
|
|
// Function untuk mencari district berdasarkan name
|
|
function getDistrictByName(name: string): District | undefined {
|
|
const regencyCode = regencyCodeRef.value
|
|
if (!regencyCode) return undefined
|
|
|
|
const districtsForRegency = districtsCache.value.get(regencyCode) || []
|
|
return districtsForRegency.find((district) => district.name.toLowerCase() === name.toLowerCase())
|
|
}
|
|
|
|
// Function untuk clear cache regency tertentu
|
|
function clearCache(regencyCodeParam?: string) {
|
|
const code = regencyCodeParam || regencyCodeRef.value
|
|
if (code) {
|
|
districtsCache.value.delete(code)
|
|
loadingStates.value.delete(code)
|
|
errorStates.value.delete(code)
|
|
}
|
|
}
|
|
|
|
// Function untuk clear semua cache
|
|
function clearAllCache() {
|
|
districtsCache.value.clear()
|
|
loadingStates.value.clear()
|
|
errorStates.value.clear()
|
|
}
|
|
|
|
// Function untuk refresh data
|
|
function refreshDistricts(regencyCodeParam?: string) {
|
|
const code = regencyCodeParam || regencyCodeRef.value
|
|
if (code) {
|
|
return fetchDistricts(code, true)
|
|
}
|
|
}
|
|
|
|
// Debounced regency code untuk mencegah multiple calls
|
|
const debouncedRegencyCode = refDebounced(regencyCodeRef, 100)
|
|
|
|
// Watch perubahan regency code untuk auto fetch
|
|
watch(
|
|
debouncedRegencyCode,
|
|
(newCode, oldCode) => {
|
|
if (newCode && newCode !== oldCode) {
|
|
// Jika ada oldCode berarti user action (ganti pilihan)
|
|
const isUserAction = !!oldCode
|
|
fetchDistricts(newCode, false, isUserAction)
|
|
}
|
|
},
|
|
{ immediate: true },
|
|
)
|
|
|
|
return {
|
|
// Data
|
|
districts: readonly(districts),
|
|
districtOptions,
|
|
|
|
// State
|
|
isLoading: readonly(isLoading),
|
|
error: readonly(error),
|
|
|
|
// Methods
|
|
fetchDistricts,
|
|
refreshDistricts,
|
|
getDistrictByCode,
|
|
getDistrictByName,
|
|
clearCache,
|
|
clearAllCache,
|
|
}
|
|
}
|
|
|
|
// Export untuk direct access ke cached data (jika diperlukan)
|
|
export const useDistrictsCache = () => ({
|
|
districtsCache: readonly(districtsCache),
|
|
loadingStates: readonly(loadingStates),
|
|
errorStates: readonly(errorStates),
|
|
})
|