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:
Khafid Prayoga
2025-10-09 18:46:58 +07:00
parent e0867cb59f
commit 5121422658
12 changed files with 485 additions and 131 deletions

No files matched your search

@@ -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
+79 -21
View File
@@ -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,
)