adjust: improve regency select based on new useRegencies composable to explicit pull all data with no limitation, because it has a province code
This commit is contained in:
@@ -8,6 +8,7 @@ import { cn } from '~/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
fieldName: string
|
||||
villageCode?: string
|
||||
isDisabled?: boolean
|
||||
placeholder?: string
|
||||
errors?: FormErrors
|
||||
@@ -26,17 +27,19 @@ const {
|
||||
fieldGroupClass,
|
||||
} = props
|
||||
|
||||
const postalCodeOptions = [
|
||||
{ label: '65120', value: '65120' },
|
||||
{ label: '65121', value: '65121' },
|
||||
{ label: '65123', value: '65123' },
|
||||
{ label: '65124', value: '65124' },
|
||||
{ label: '65125', value: '65125' },
|
||||
{ label: '65126', value: '65126' },
|
||||
{ label: '65127', value: '65127' },
|
||||
{ label: '65128', value: '65128' },
|
||||
{ label: '65129', value: '65129' },
|
||||
]
|
||||
const villageCodeRef = toRef(props, 'villageCode')
|
||||
const { postalCodeOptions, isLoading, error } = usePostalCodes(villageCodeRef)
|
||||
|
||||
const dynamicPlaceholder = computed(() => {
|
||||
if (!props.villageCode) return 'Pilih kelurahan terlebih dahulu'
|
||||
if (isLoading.value) return 'Memuat kode pos...'
|
||||
if (error.value) return 'Gagal memuat data'
|
||||
return placeholder
|
||||
})
|
||||
|
||||
const isFieldDisabled = computed(() => {
|
||||
return props.isDisabled || !props.villageCode || isLoading.value || !!error.value
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -62,8 +65,8 @@ const postalCodeOptions = [
|
||||
:id="fieldName"
|
||||
v-bind="componentField"
|
||||
:items="postalCodeOptions"
|
||||
:placeholder="placeholder"
|
||||
:is-disabled="isDisabled"
|
||||
:placeholder="dynamicPlaceholder"
|
||||
:is-disabled="isFieldDisabled"
|
||||
search-placeholder="Cari..."
|
||||
empty-message="Kode pos tidak ditemukan"
|
||||
/>
|
||||
|
||||
@@ -28,7 +28,7 @@ const {
|
||||
|
||||
// Gunakan composable untuk mengelola data regencies
|
||||
const provinceCodeRef = toRef(props, 'provinceCode')
|
||||
const { regencyOptions, isLoading, error } = useRegencies(provinceCodeRef)
|
||||
const { regencyOptions, isLoading, error } = useRegencies({ provinceCode: provinceCodeRef, enablePagination: false })
|
||||
|
||||
// Computed untuk menentukan placeholder berdasarkan state
|
||||
const dynamicPlaceholder = computed(() => {
|
||||
|
||||
@@ -6,7 +6,8 @@ import FieldGroup from '~/components/pub/my-ui/form/field-group.vue'
|
||||
import Field from '~/components/pub/my-ui/form/field.vue'
|
||||
import InputBase from '~/components/pub/my-ui/form/input-base.vue'
|
||||
import Label from '~/components/pub/my-ui/form/label.vue'
|
||||
import RadioResidence from './_common/radio-residence.vue'
|
||||
import { Label as RadioLabel } from '~/components/pub/ui/label'
|
||||
import { RadioGroup, RadioGroupItem } from '~/components/pub/ui/radio-group'
|
||||
import SelectDistrict from './_common/select-district.vue'
|
||||
import SelectPostal from './_common/select-postal.vue'
|
||||
import SelectProvince from './_common/select-province.vue'
|
||||
@@ -39,18 +40,27 @@ let isResetting = false
|
||||
|
||||
// Field dependency map for placeholder
|
||||
const fieldStates: Record<string, { dependsOn?: string; placeholder: string }> = {
|
||||
regencyId: { dependsOn: 'provinceCode', placeholder: 'Pilih provinsi dahulu' },
|
||||
districtId: { dependsOn: 'regencyId', placeholder: 'Pilih kabupaten/kota dahulu' },
|
||||
villageId: { dependsOn: 'districtId', placeholder: 'Pilih kecamatan dahulu' },
|
||||
zipCode: { dependsOn: 'villageId', placeholder: 'Pilih kelurahan dahulu' },
|
||||
regency_code: { dependsOn: 'province_code', placeholder: 'Pilih provinsi dahulu' },
|
||||
district_code: { dependsOn: 'regency_code', placeholder: 'Pilih kabupaten/kota dahulu' },
|
||||
village_code: { dependsOn: 'district_code', placeholder: 'Pilih kecamatan dahulu' },
|
||||
postal_code: { dependsOn: 'village_code', placeholder: 'Pilih kelurahan dahulu' },
|
||||
address: { placeholder: 'Masukkan alamat' },
|
||||
rt: { placeholder: '001' },
|
||||
rw: { placeholder: '002' },
|
||||
}
|
||||
|
||||
// Computed untuk konversi boolean ke string untuk radio group
|
||||
const isSameAddressString = computed(() => {
|
||||
const value = formRef.value?.values?.isSameAddress
|
||||
if (typeof value === 'boolean') {
|
||||
return value ? '1' : '0'
|
||||
}
|
||||
return value || '1'
|
||||
})
|
||||
// #region Function Helper
|
||||
function getFieldState(field: string) {
|
||||
const state = fieldStates[field]
|
||||
const isSame = formRef.value?.values?.isSameAddress === '1'
|
||||
const isSame = formRef.value?.values?.isSameAddress === true || formRef.value?.values?.isSameAddress === '1'
|
||||
|
||||
// Jika alamat sama, semua field kecuali provinsi disabled
|
||||
if (['address', 'rt', 'rw'].includes(field) && isSame) {
|
||||
@@ -63,7 +73,7 @@ function getFieldState(field: string) {
|
||||
const isDisabledByDependency = !dependencyValue
|
||||
|
||||
// Jika isSame, semua field location disabled
|
||||
if (isSame && ['regencyId', 'districtId', 'villageId', 'zipCode'].includes(field)) {
|
||||
if (isSame && ['regency_code', 'district_code', 'village_code', 'postal_code'].includes(field)) {
|
||||
return { placeholder: '-', disabled: true }
|
||||
}
|
||||
|
||||
@@ -73,7 +83,7 @@ function getFieldState(field: string) {
|
||||
}
|
||||
|
||||
// Jika isSame dan field location, disabled
|
||||
if (isSame && ['regencyId', 'districtId', 'villageId', 'zipCode'].includes(field)) {
|
||||
if (isSame && ['regency_code', 'district_code', 'village_code', 'postal_code'].includes(field)) {
|
||||
return { placeholder: '-', disabled: true }
|
||||
}
|
||||
|
||||
@@ -84,9 +94,9 @@ function getFieldState(field: string) {
|
||||
|
||||
// #region watch
|
||||
|
||||
// Watch provinceCode changes
|
||||
// Watch province_code changes
|
||||
watch(
|
||||
() => formRef.value?.values?.provinceCode,
|
||||
() => formRef.value?.values?.province_code,
|
||||
(newValue, oldValue) => {
|
||||
if (isResetting || !formRef.value || newValue === oldValue) return
|
||||
|
||||
@@ -95,10 +105,10 @@ watch(
|
||||
|
||||
formRef.value.setValues(
|
||||
{
|
||||
regencyId: undefined,
|
||||
districtId: undefined,
|
||||
villageId: undefined,
|
||||
zipCode: undefined,
|
||||
regency_code: undefined,
|
||||
district_code: undefined,
|
||||
village_code: undefined,
|
||||
postal_code: undefined,
|
||||
},
|
||||
false,
|
||||
)
|
||||
@@ -110,9 +120,9 @@ watch(
|
||||
},
|
||||
)
|
||||
|
||||
// Watch regencyId changes
|
||||
// Watch regency_code changes
|
||||
watch(
|
||||
() => formRef.value?.values?.regencyId,
|
||||
() => formRef.value?.values?.regency_code,
|
||||
(newValue, oldValue) => {
|
||||
if (isResetting || !formRef.value || newValue === oldValue) return
|
||||
|
||||
@@ -121,9 +131,9 @@ watch(
|
||||
|
||||
formRef.value.setValues(
|
||||
{
|
||||
districtId: undefined,
|
||||
villageId: undefined,
|
||||
zipCode: undefined,
|
||||
district_code: undefined,
|
||||
village_code: undefined,
|
||||
postal_code: undefined,
|
||||
},
|
||||
false,
|
||||
)
|
||||
@@ -135,9 +145,9 @@ watch(
|
||||
},
|
||||
)
|
||||
|
||||
// Watch districtId changes
|
||||
// Watch district_code changes
|
||||
watch(
|
||||
() => formRef.value?.values?.districtId,
|
||||
() => formRef.value?.values?.district_code,
|
||||
(newValue, oldValue) => {
|
||||
if (isResetting || !formRef.value || newValue === oldValue) return
|
||||
|
||||
@@ -146,8 +156,8 @@ watch(
|
||||
|
||||
formRef.value.setValues(
|
||||
{
|
||||
villageId: undefined,
|
||||
zipCode: undefined,
|
||||
village_code: undefined,
|
||||
postal_code: undefined,
|
||||
},
|
||||
false,
|
||||
)
|
||||
@@ -159,9 +169,9 @@ watch(
|
||||
},
|
||||
)
|
||||
|
||||
// Watch villageId changes
|
||||
// Watch village_code changes
|
||||
watch(
|
||||
() => formRef.value?.values?.villageId,
|
||||
() => formRef.value?.values?.village_code,
|
||||
(newValue, oldValue) => {
|
||||
if (isResetting || !formRef.value || newValue === oldValue) return
|
||||
|
||||
@@ -170,7 +180,7 @@ watch(
|
||||
|
||||
formRef.value.setValues(
|
||||
{
|
||||
zipCode: undefined,
|
||||
postal_code: undefined,
|
||||
},
|
||||
false,
|
||||
)
|
||||
@@ -188,19 +198,23 @@ watch(
|
||||
(newValue, oldValue) => {
|
||||
if (!formRef.value || newValue === oldValue) return
|
||||
|
||||
// Ketika berubah dari '1' ke '0', clear empty strings dan trigger validasi
|
||||
if (oldValue === '1' && newValue === '0') {
|
||||
// Konversi ke boolean untuk perbandingan yang konsisten
|
||||
const newBool = newValue === true || newValue === '1'
|
||||
const oldBool = oldValue === true || oldValue === '1'
|
||||
|
||||
// Ketika berubah dari true ke false, clear empty strings dan trigger validasi
|
||||
if (oldBool && !newBool) {
|
||||
nextTick(() => {
|
||||
// Set empty strings ke undefined untuk trigger required validation
|
||||
const currentValues = formRef.value.values
|
||||
const updatedValues = { ...currentValues }
|
||||
|
||||
// Convert empty strings to undefined untuk field yang sekarang required
|
||||
if (updatedValues.provinceCode === '') updatedValues.provinceCode = undefined
|
||||
if (updatedValues.regencyId === '') updatedValues.regencyId = undefined
|
||||
if (updatedValues.districtId === '') updatedValues.districtId = undefined
|
||||
if (updatedValues.villageId === '') updatedValues.villageId = undefined
|
||||
if (updatedValues.zipCode === '') updatedValues.zipCode = undefined
|
||||
if (updatedValues.province_code === '') updatedValues.province_code = undefined
|
||||
if (updatedValues.regency_code === '') updatedValues.regency_code = undefined
|
||||
if (updatedValues.district_code === '') updatedValues.district_code = undefined
|
||||
if (updatedValues.village_code === '') updatedValues.village_code = undefined
|
||||
if (updatedValues.postal_code === '') updatedValues.postal_code = undefined
|
||||
if (updatedValues.address === '') updatedValues.address = undefined
|
||||
|
||||
// Update values dan trigger validasi
|
||||
@@ -213,15 +227,15 @@ watch(
|
||||
})
|
||||
}
|
||||
|
||||
// Ketika berubah dari '0' ke '1', clear error messages
|
||||
if (oldValue === '0' && newValue === '1') {
|
||||
// Ketika berubah dari false ke true, clear error messages
|
||||
if (!oldBool && newBool) {
|
||||
nextTick(() => {
|
||||
// Clear error messages untuk field yang tidak lagi required
|
||||
formRef.value?.setFieldError('provinceCode', undefined)
|
||||
formRef.value?.setFieldError('regencyId', undefined)
|
||||
formRef.value?.setFieldError('districtId', undefined)
|
||||
formRef.value?.setFieldError('villageId', undefined)
|
||||
formRef.value?.setFieldError('zipCode', undefined)
|
||||
formRef.value?.setFieldError('province_code', undefined)
|
||||
formRef.value?.setFieldError('regency_code', undefined)
|
||||
formRef.value?.setFieldError('district_code', undefined)
|
||||
formRef.value?.setFieldError('village_code', undefined)
|
||||
formRef.value?.setFieldError('postal_code', undefined)
|
||||
formRef.value?.setFieldError('address', undefined)
|
||||
formRef.value?.setFieldError('rt', undefined)
|
||||
formRef.value?.setFieldError('rw', undefined)
|
||||
@@ -281,25 +295,74 @@ watch(
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
<RadioResidence field-name="isSameAddress" />
|
||||
<FieldGroup class="radio-group-field">
|
||||
<Label
|
||||
size="fit"
|
||||
height="compact"
|
||||
label-for="isSameAddress"
|
||||
>
|
||||
Apakah alamat KTP sama dengan alamat sekarang?
|
||||
</Label>
|
||||
<Field
|
||||
id="isSameAddress"
|
||||
:errors="errors"
|
||||
>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
name="isSameAddress"
|
||||
>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<RadioGroup
|
||||
:model-value="isSameAddressString"
|
||||
@update:model-value="(value) => componentField.onChange(value)"
|
||||
class="flex flex-row flex-wrap gap-4 sm:gap-6"
|
||||
>
|
||||
<div
|
||||
v-for="(option, index) in [
|
||||
{ label: 'Ya', value: '1' },
|
||||
{ label: 'Tidak', value: '0' },
|
||||
]"
|
||||
:key="option.value"
|
||||
class="flex min-w-fit items-center space-x-2"
|
||||
>
|
||||
<RadioGroupItem
|
||||
:id="`isSameAddress-${index}`"
|
||||
:value="option.value"
|
||||
class="relative h-4 w-4 rounded-full border-2 border-muted-foreground before:absolute before:inset-1 before:rounded-full before:bg-primary before:opacity-0 before:transition-opacity data-[state=checked]:border-primary data-[state=checked]:bg-white data-[state=checked]:before:opacity-100 sm:h-5 sm:w-5"
|
||||
/>
|
||||
<RadioLabel
|
||||
:for="`isSameAddress-${index}`"
|
||||
class="cursor-pointer select-none text-xs font-medium leading-none transition-colors hover:text-primary peer-disabled:cursor-not-allowed peer-disabled:opacity-70 sm:text-sm"
|
||||
>
|
||||
{{ option.label }}
|
||||
</RadioLabel>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
<FormMessage class="ml-0 mt-1" />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
<Block></Block>
|
||||
|
||||
<div class="flex-row gap-2 md:flex">
|
||||
<div class="min-w-0 flex-1">
|
||||
<SelectProvince
|
||||
field-name="provinceCode"
|
||||
field-name="province_code"
|
||||
placeholder="Pilih"
|
||||
:is-disabled="values.isSameAddress === '1'"
|
||||
:is-required="values.isSameAddress !== '1'"
|
||||
:is-disabled="values.isSameAddress === true || values.isSameAddress === '1'"
|
||||
:is-required="values.isSameAddress !== true && values.isSameAddress !== '1'"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="min-w-0 flex-1">
|
||||
<SelectRegency
|
||||
field-name="regencyId"
|
||||
:province-code="values.provinceCode"
|
||||
:is-disabled="getFieldState('regencyId').disabled"
|
||||
:is-required="values.isSameAddress !== '1'"
|
||||
field-name="regency_code"
|
||||
:province-code="values.province_code"
|
||||
:is-disabled="getFieldState('regency_code').disabled"
|
||||
:is-required="values.isSameAddress !== true && values.isSameAddress !== '1'"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -307,18 +370,18 @@ watch(
|
||||
<div class="flex-row gap-2 md:flex">
|
||||
<div class="min-w-0 flex-1">
|
||||
<SelectDistrict
|
||||
field-name="districtId"
|
||||
:regency-code="values.regencyId"
|
||||
:is-disabled="getFieldState('districtId').disabled"
|
||||
:is-required="values.isSameAddress !== '1'"
|
||||
field-name="district_code"
|
||||
:regency-code="values.regency_code"
|
||||
:is-disabled="getFieldState('district_code').disabled"
|
||||
:is-required="values.isSameAddress !== true && values.isSameAddress !== '1'"
|
||||
/>
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<SelectVillage
|
||||
field-name="villageId"
|
||||
:district-code="values.districtId"
|
||||
:is-disabled="getFieldState('villageId').disabled"
|
||||
:is-required="values.isSameAddress !== '1'"
|
||||
field-name="village_code"
|
||||
:district-code="values.district_code"
|
||||
:is-disabled="getFieldState('village_code').disabled"
|
||||
:is-required="values.isSameAddress !== true && values.isSameAddress !== '1'"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -328,7 +391,7 @@ watch(
|
||||
:placeholder="getFieldState('address').placeholder"
|
||||
:is-disabled="getFieldState('address').disabled"
|
||||
:errors="errors"
|
||||
:is-required="values.isSameAddress !== '1'"
|
||||
:is-required="values.isSameAddress !== true && values.isSameAddress !== '1'"
|
||||
/>
|
||||
<div class="flex-row gap-2 md:flex">
|
||||
<div class="min-w-0 flex-1">
|
||||
@@ -337,7 +400,7 @@ watch(
|
||||
label="RT"
|
||||
:errors="errors"
|
||||
numeric-only
|
||||
:max-length="3"
|
||||
:max-length="2"
|
||||
:placeholder="getFieldState('rt').placeholder"
|
||||
:is-disabled="getFieldState('rt').disabled"
|
||||
/>
|
||||
@@ -350,16 +413,17 @@ watch(
|
||||
:placeholder="getFieldState('rw').placeholder"
|
||||
:is-disabled="getFieldState('rw').disabled"
|
||||
:errors="errors"
|
||||
:max-length="3"
|
||||
:max-length="2"
|
||||
numeric-only
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="min-w-0 flex-[2]">
|
||||
<SelectPostal
|
||||
field-name="zipCode"
|
||||
:placeholder="getFieldState('zipCode').placeholder"
|
||||
:is-disabled="getFieldState('zipCode').disabled || !values.villageId"
|
||||
field-name="postal_code"
|
||||
:village-code="values.village_code"
|
||||
:placeholder="getFieldState('postal_code').placeholder"
|
||||
:is-disabled="getFieldState('postal_code').disabled || !values.village_code"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -204,7 +204,7 @@ watch(
|
||||
<div class="flex-row gap-2 md:flex">
|
||||
<div class="min-w-0 flex-1">
|
||||
<SelectProvince
|
||||
field-name="provinceCode"
|
||||
field-name="province_code"
|
||||
placeholder="Pilih"
|
||||
is-required
|
||||
/>
|
||||
@@ -212,8 +212,8 @@ watch(
|
||||
|
||||
<div class="min-w-0 flex-1">
|
||||
<SelectRegency
|
||||
field-name="regencyId"
|
||||
:province-code="values.provinceCode"
|
||||
field-name="regency_code"
|
||||
:province-code="values.province_code"
|
||||
is-required
|
||||
/>
|
||||
</div>
|
||||
@@ -222,15 +222,15 @@ watch(
|
||||
<div class="flex-row gap-2 md:flex">
|
||||
<div class="min-w-0 flex-1">
|
||||
<SelectDistrict
|
||||
field-name="districtId"
|
||||
:regency-code="values.regencyId"
|
||||
field-name="district_code"
|
||||
:regency-code="values.regency_code"
|
||||
is-required
|
||||
/>
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<SelectVillage
|
||||
field-name="villageId"
|
||||
:district-code="values.districtId"
|
||||
field-name="village_code"
|
||||
:district-code="values.district_code"
|
||||
is-required
|
||||
/>
|
||||
</div>
|
||||
@@ -248,10 +248,10 @@ watch(
|
||||
<InputBase
|
||||
field-name="rt"
|
||||
label="RT"
|
||||
placeholder="001"
|
||||
placeholder="01"
|
||||
:errors="errors"
|
||||
numeric-only
|
||||
:max-length="3"
|
||||
:max-length="2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -259,18 +259,19 @@ watch(
|
||||
<InputBase
|
||||
field-name="rw"
|
||||
label="RW"
|
||||
placeholder="002"
|
||||
placeholder="02"
|
||||
:errors="errors"
|
||||
:max-length="3"
|
||||
:max-length="2"
|
||||
numeric-only
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="min-w-0 flex-[2]">
|
||||
<SelectPostal
|
||||
field-name="zipCode"
|
||||
field-name="postal_code"
|
||||
placeholder="Pilih kelurahan dahulu"
|
||||
:is-disabled="!values.villageId"
|
||||
:village-code="values.village_code"
|
||||
:is-disabled="!values.village_code"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -94,6 +94,7 @@ defineExpose({
|
||||
:disabled="fields.length >= contactLimit"
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
@click="push({ contactType: '', contactNumber: '' })"
|
||||
>
|
||||
<Icon
|
||||
|
||||
@@ -28,6 +28,31 @@ const personPatientForm = ref<ExposedForm<any> | null>(null)
|
||||
// #endregion
|
||||
|
||||
// #region Lifecycle Hooks
|
||||
onMounted(() => {
|
||||
// Initial synchronization when forms are mounted and isSameAddress is true by default
|
||||
nextTick(() => {
|
||||
const isSameAddress = personAddressRelativeForm.value?.values?.isSameAddress
|
||||
if ((isSameAddress === true || isSameAddress === '1') && personAddressForm.value?.values && personAddressRelativeForm.value) {
|
||||
const currentAddressValues = personAddressForm.value.values
|
||||
if (Object.keys(currentAddressValues).length > 0) {
|
||||
personAddressRelativeForm.value.setValues(
|
||||
{
|
||||
...personAddressRelativeForm.value.values,
|
||||
province_code: currentAddressValues.province_code || undefined,
|
||||
regency_code: currentAddressValues.regency_code || undefined,
|
||||
district_code: currentAddressValues.district_code || undefined,
|
||||
village_code: currentAddressValues.village_code || undefined,
|
||||
postal_code: currentAddressValues.postal_code || undefined,
|
||||
address: currentAddressValues.address || undefined,
|
||||
rt: currentAddressValues.rt || undefined,
|
||||
rw: currentAddressValues.rw || undefined,
|
||||
},
|
||||
false,
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
// #endregion
|
||||
|
||||
// #region Functions
|
||||
@@ -82,26 +107,59 @@ async function submitAll() {
|
||||
// #endregion
|
||||
|
||||
// #region Watchers
|
||||
// Watcher untuk sinkronisasi alamat ketika isSameAddress = '1'
|
||||
// Watcher untuk sinkronisasi initial ketika kedua form sudah ready
|
||||
watch(
|
||||
[() => personAddressForm.value, () => personAddressRelativeForm.value],
|
||||
([addressForm, relativeForm]) => {
|
||||
if (addressForm && relativeForm) {
|
||||
// Trigger initial sync jika isSameAddress adalah true
|
||||
nextTick(() => {
|
||||
const isSameAddress = relativeForm.values?.isSameAddress
|
||||
if ((isSameAddress === true || isSameAddress === '1') && addressForm.values) {
|
||||
const currentAddressValues = addressForm.values
|
||||
if (Object.keys(currentAddressValues).length > 0) {
|
||||
relativeForm.setValues(
|
||||
{
|
||||
...relativeForm.values,
|
||||
province_code: currentAddressValues.province_code || undefined,
|
||||
regency_code: currentAddressValues.regency_code || undefined,
|
||||
district_code: currentAddressValues.district_code || undefined,
|
||||
village_code: currentAddressValues.village_code || undefined,
|
||||
postal_code: currentAddressValues.postal_code || undefined,
|
||||
address: currentAddressValues.address || undefined,
|
||||
rt: currentAddressValues.rt || undefined,
|
||||
rw: currentAddressValues.rw || undefined,
|
||||
},
|
||||
false,
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
// Watcher untuk sinkronisasi alamat ketika isSameAddress = true
|
||||
watch(
|
||||
() => personAddressForm.value?.values,
|
||||
(newAddressValues) => {
|
||||
// Cek apakah alamat KTP harus sama dengan alamat sekarang
|
||||
const isSameAddress = personAddressRelativeForm.value?.values?.isSameAddress === '1'
|
||||
const isSameAddress = personAddressRelativeForm.value?.values?.isSameAddress
|
||||
|
||||
if (isSameAddress && newAddressValues && personAddressRelativeForm.value) {
|
||||
if ((isSameAddress === true || isSameAddress === '1') && newAddressValues && personAddressRelativeForm.value) {
|
||||
// Sinkronkan semua field alamat dari alamat sekarang ke alamat KTP
|
||||
personAddressRelativeForm.value.setValues(
|
||||
{
|
||||
...personAddressRelativeForm.value.values,
|
||||
provinceCode: newAddressValues.provinceCode || '',
|
||||
regencyId: newAddressValues.regencyId || '',
|
||||
districtId: newAddressValues.districtId || '',
|
||||
villageId: newAddressValues.villageId || '',
|
||||
zipCode: newAddressValues.zipCode || '',
|
||||
address: newAddressValues.address || '',
|
||||
rt: newAddressValues.rt || '',
|
||||
rw: newAddressValues.rw || '',
|
||||
province_code: newAddressValues.province_code || undefined,
|
||||
regency_code: newAddressValues.regency_code || undefined,
|
||||
district_code: newAddressValues.district_code || undefined,
|
||||
village_code: newAddressValues.village_code || undefined,
|
||||
postal_code: newAddressValues.postal_code || undefined,
|
||||
address: newAddressValues.address || undefined,
|
||||
rt: newAddressValues.rt || undefined,
|
||||
rw: newAddressValues.rw || undefined,
|
||||
},
|
||||
false,
|
||||
)
|
||||
@@ -114,20 +172,20 @@ watch(
|
||||
watch(
|
||||
() => personAddressRelativeForm.value?.values?.isSameAddress,
|
||||
(isSameAddress) => {
|
||||
if (isSameAddress === '1' && personAddressForm.value?.values && personAddressRelativeForm.value) {
|
||||
// Ketika isSameAddress diubah menjadi '1', copy alamat sekarang ke alamat KTP
|
||||
if ((isSameAddress === true || isSameAddress === '1') && personAddressForm.value?.values && personAddressRelativeForm.value) {
|
||||
// Ketika isSameAddress diubah menjadi true, copy alamat sekarang ke alamat KTP
|
||||
const currentAddressValues = personAddressForm.value.values
|
||||
personAddressRelativeForm.value.setValues(
|
||||
{
|
||||
...personAddressRelativeForm.value.values,
|
||||
provinceCode: currentAddressValues.provinceCode || '',
|
||||
regencyId: currentAddressValues.regencyId || '',
|
||||
districtId: currentAddressValues.districtId || '',
|
||||
villageId: currentAddressValues.villageId || '',
|
||||
zipCode: currentAddressValues.zipCode || '',
|
||||
address: currentAddressValues.address || '',
|
||||
rt: currentAddressValues.rt || '',
|
||||
rw: currentAddressValues.rw || '',
|
||||
province_code: currentAddressValues.province_code || undefined,
|
||||
regency_code: currentAddressValues.regency_code || undefined,
|
||||
district_code: currentAddressValues.district_code || undefined,
|
||||
village_code: currentAddressValues.village_code || undefined,
|
||||
postal_code: currentAddressValues.postal_code || undefined,
|
||||
address: currentAddressValues.address || undefined,
|
||||
rt: currentAddressValues.rt || undefined,
|
||||
rw: currentAddressValues.rw || undefined,
|
||||
},
|
||||
false,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
import { ref, computed, watch, readonly, type Ref } from 'vue'
|
||||
import { refDebounced } from '@vueuse/core'
|
||||
import type { PostalCode } from '~/models/postal-code'
|
||||
import type { SelectItem } from '~/components/pub/my-ui/form/select.vue'
|
||||
import * as postalCodeService from '../services/postal-code.service'
|
||||
|
||||
// Global cache untuk postal codes berdasarkan village code
|
||||
const postalCodesCache = ref<Map<string, PostalCode[]>>(new Map())
|
||||
const loadingStates = ref<Map<string, boolean>>(new Map())
|
||||
const errorStates = ref<Map<string, string | null>>(new Map())
|
||||
|
||||
export function usePostalCodes(villageCode: Ref<string | undefined> | string | undefined) {
|
||||
// Convert villageCode ke ref jika bukan ref
|
||||
const villageCodeRef = typeof villageCode === 'string' || villageCode === undefined ? ref(villageCode) : villageCode
|
||||
|
||||
// Computed untuk mendapatkan postalCodes berdasarkan village code
|
||||
const postalCodes = computed(() => {
|
||||
const code = villageCodeRef.value
|
||||
if (!code) return []
|
||||
return postalCodesCache.value.get(code) || []
|
||||
})
|
||||
|
||||
// Computed untuk loading state
|
||||
const isLoading = computed(() => {
|
||||
const code = villageCodeRef.value
|
||||
if (!code) return false
|
||||
return loadingStates.value.get(code) || false
|
||||
})
|
||||
|
||||
// Computed untuk error state
|
||||
const error = computed(() => {
|
||||
const code = villageCodeRef.value
|
||||
if (!code) return null
|
||||
return errorStates.value.get(code) || null
|
||||
})
|
||||
|
||||
// Computed untuk format SelectItem
|
||||
const postalCodeOptions = computed<SelectItem[]>(() => {
|
||||
return postalCodes.value.map((postalCode) => ({
|
||||
label: postalCode.code,
|
||||
value: postalCode.code,
|
||||
searchValue: postalCode.code,
|
||||
}))
|
||||
})
|
||||
|
||||
// Function untuk fetch postalCodes berdasarkan village code
|
||||
async function fetchPostalCodes(villageCodeParam?: string, forceRefresh = false, isUserAction = false) {
|
||||
const code = villageCodeParam || villageCodeRef.value
|
||||
if (!code) return
|
||||
|
||||
// Jika user action atau force refresh, selalu fetch
|
||||
// Jika bukan user action dan sudah ada cache, skip
|
||||
if (!isUserAction && !forceRefresh && postalCodesCache.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 postalCodeService.getList({
|
||||
sort: 'code:asc',
|
||||
'village-code': code,
|
||||
'page-no-limit': true,
|
||||
})
|
||||
|
||||
if (response.success) {
|
||||
const postalCodesData = response.body.data || []
|
||||
postalCodesCache.value.set(code, postalCodesData)
|
||||
} else {
|
||||
errorStates.value.set(code, 'Gagal memuat data kode pos')
|
||||
console.error('Failed to fetch postal codes:', response)
|
||||
}
|
||||
} catch (err) {
|
||||
errorStates.value.set(code, 'Terjadi kesalahan saat memuat data kode pos')
|
||||
console.error('Error fetching postal codes:', err)
|
||||
} finally {
|
||||
loadingStates.value.set(code, false)
|
||||
loadingStates.value.delete(pendingKey)
|
||||
}
|
||||
}
|
||||
|
||||
// Function untuk mencari postalCode berdasarkan code
|
||||
function getPostalCodeByCode(code: string): PostalCode | undefined {
|
||||
const villageCode = villageCodeRef.value
|
||||
if (!villageCode) return undefined
|
||||
|
||||
const postalCodesForVillage = postalCodesCache.value.get(villageCode) || []
|
||||
return postalCodesForVillage.find((postalCode) => postalCode.code === code)
|
||||
}
|
||||
|
||||
// Function untuk clear cache village tertentu
|
||||
function clearCache(villageCodeParam?: string) {
|
||||
const code = villageCodeParam || villageCodeRef.value
|
||||
if (code) {
|
||||
postalCodesCache.value.delete(code)
|
||||
loadingStates.value.delete(code)
|
||||
errorStates.value.delete(code)
|
||||
}
|
||||
}
|
||||
|
||||
// Function untuk clear semua cache
|
||||
function clearAllCache() {
|
||||
postalCodesCache.value.clear()
|
||||
loadingStates.value.clear()
|
||||
errorStates.value.clear()
|
||||
}
|
||||
|
||||
// Function untuk refresh data
|
||||
function refreshPostalCodes(villageCodeParam?: string) {
|
||||
const code = villageCodeParam || villageCodeRef.value
|
||||
if (code) {
|
||||
return fetchPostalCodes(code, true)
|
||||
}
|
||||
}
|
||||
|
||||
// Debounced village code untuk mencegah multiple calls
|
||||
const debouncedVillageCode = refDebounced(villageCodeRef, 100)
|
||||
|
||||
// Watch perubahan village code untuk auto fetch
|
||||
watch(
|
||||
debouncedVillageCode,
|
||||
(newCode, oldCode) => {
|
||||
if (newCode && newCode !== oldCode) {
|
||||
// Jika ada oldCode berarti user action (ganti pilihan)
|
||||
const isUserAction = !!oldCode
|
||||
fetchPostalCodes(newCode, false, isUserAction)
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
return {
|
||||
// Data
|
||||
postalCodes: readonly(postalCodes),
|
||||
postalCodeOptions,
|
||||
|
||||
// State
|
||||
isLoading: readonly(isLoading),
|
||||
error: readonly(error),
|
||||
|
||||
// Methods
|
||||
fetchPostalCodes,
|
||||
refreshPostalCodes,
|
||||
getPostalCodeByCode,
|
||||
clearCache,
|
||||
clearAllCache,
|
||||
}
|
||||
}
|
||||
|
||||
// Export untuk direct access ke cached data (jika diperlukan)
|
||||
export const usePostalCodesCache = () => ({
|
||||
postalCodesCache: readonly(postalCodesCache),
|
||||
loadingStates: readonly(loadingStates),
|
||||
errorStates: readonly(errorStates),
|
||||
})
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type Base, genBase } from "./_base"
|
||||
import { type Base, genBase } from './_base'
|
||||
|
||||
export interface PersonAddress extends Base {
|
||||
person_id: number
|
||||
@@ -6,7 +6,7 @@ export interface PersonAddress extends Base {
|
||||
address: string
|
||||
rt?: string
|
||||
rw?: string
|
||||
postalCode?: string
|
||||
postal_code?: string
|
||||
village_code: string
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { type Base, genBase } from './_base'
|
||||
|
||||
export interface PostalCode extends Base {
|
||||
code: string
|
||||
village_code: string
|
||||
}
|
||||
|
||||
export function genPostalCode(): PostalCode {
|
||||
return {
|
||||
...genBase(),
|
||||
code: '',
|
||||
village_code: '',
|
||||
}
|
||||
}
|
||||
@@ -1,31 +1,59 @@
|
||||
import { z } from 'zod'
|
||||
import { PersonAddressSchema } from './person-address.schema'
|
||||
|
||||
// Schema untuk alamat opsional ketika isSameAddress = '1'
|
||||
const OptionalAddressSchema = PersonAddressSchema.partial()
|
||||
|
||||
// Schema untuk alamat required ketika isSameAddress = '0'
|
||||
const RequiredAddressSchema = PersonAddressSchema
|
||||
|
||||
const PersonAddressRelativeSchema = z
|
||||
.discriminatedUnion('isSameAddress', [
|
||||
z
|
||||
.object({
|
||||
isSameAddress: z.literal('1').default('1'),
|
||||
})
|
||||
.merge(OptionalAddressSchema),
|
||||
.object({
|
||||
isSameAddress: z
|
||||
.union([z.literal('1'), z.literal('0'), z.boolean()])
|
||||
.default('1')
|
||||
.transform((val) => {
|
||||
if (typeof val === 'boolean') return val ? '1' : '0'
|
||||
return val
|
||||
}),
|
||||
})
|
||||
.merge(PersonAddressSchema.partial())
|
||||
.superRefine((data, ctx) => {
|
||||
const isSameAddress = data.isSameAddress
|
||||
|
||||
z
|
||||
.object({
|
||||
isSameAddress: z.literal('0'),
|
||||
// Jika alamat tidak sama ('0'), maka semua field address wajib diisi
|
||||
if (isSameAddress === '0') {
|
||||
const requiredFields = [
|
||||
'province_code',
|
||||
'regency_code',
|
||||
'district_code',
|
||||
'village_code',
|
||||
'postal_code',
|
||||
'address'
|
||||
]
|
||||
|
||||
requiredFields.forEach((field) => {
|
||||
if (!data[field as keyof typeof data]) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: getRequiredMessage(field),
|
||||
path: [field],
|
||||
})
|
||||
}
|
||||
})
|
||||
.merge(RequiredAddressSchema),
|
||||
])
|
||||
}
|
||||
})
|
||||
.transform((data) => ({
|
||||
...data,
|
||||
isSameAddress: data.isSameAddress === '1',
|
||||
}))
|
||||
|
||||
function getRequiredMessage(field: string): string {
|
||||
const messages: Record<string, string> = {
|
||||
province_code: 'Mohon pilih provinsi',
|
||||
regency_code: 'Mohon pilih kabupaten/kota',
|
||||
district_code: 'Mohon pilih kecamatan',
|
||||
village_code: 'Mohon pilih kelurahan',
|
||||
postal_code: 'Mohon lengkapi kode pos',
|
||||
address: 'Mohon lengkapi alamat',
|
||||
}
|
||||
return messages[field] || `${field} wajib diisi`
|
||||
}
|
||||
|
||||
type PersonAddressRelativeFormData = z.infer<typeof PersonAddressRelativeSchema>
|
||||
|
||||
export { PersonAddressRelativeSchema }
|
||||
|
||||
@@ -16,8 +16,7 @@ const PersonAddressSchema = z.object({
|
||||
village_code: z.string({
|
||||
required_error: 'Mohon pilih kelurahan',
|
||||
}),
|
||||
// diganti postalCode, zipCode hanya beberapa negara, kurang universal
|
||||
postalCode: z.string({
|
||||
postal_code: z.string({
|
||||
required_error: 'Mohon lengkapi kode pos',
|
||||
}),
|
||||
// .min(5, 'Kode pos harus berupa angka 5 digit')
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
// Base service for postal code operations
|
||||
import * as base from './_crud-base'
|
||||
|
||||
const path = '/api/v1/postal-code'
|
||||
const name = 'postal-code'
|
||||
|
||||
export function create(data: any) {
|
||||
return base.create(path, data, name)
|
||||
}
|
||||
|
||||
export function getList(params: any = null) {
|
||||
return base.getList(path, params, name)
|
||||
}
|
||||
|
||||
export function getDetail(id: number | string) {
|
||||
return base.getDetail(path, id, name)
|
||||
}
|
||||
Reference in New Issue
Block a user