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
This commit is contained in:
-48
@@ -3,15 +3,12 @@ import type { FormErrors } from '~/types/error'
|
||||
import FieldGroup from '~/components/pub/my-ui/form/field-group.vue'
|
||||
import Field from '~/components/pub/my-ui/form/field.vue'
|
||||
import Label from '~/components/pub/my-ui/form/label.vue'
|
||||
import Select from '~/components/pub/my-ui/form/select.vue'
|
||||
import { Input } from '~/components/pub/ui/input'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
defineProps<{
|
||||
fieldNameAlias: string
|
||||
fieldNameInput: string
|
||||
placeholder: string
|
||||
labelForAlias: string
|
||||
labelForInput: string
|
||||
errors?: FormErrors
|
||||
class?: string
|
||||
@@ -21,54 +18,9 @@ defineProps<{
|
||||
maxLength?: number
|
||||
isRequired?: boolean
|
||||
}>()
|
||||
|
||||
const aliasOptions = [
|
||||
{ label: 'An', value: 'an' },
|
||||
{ label: 'By.Ny', value: 'byny' },
|
||||
{ label: 'Nn', value: 'nn' },
|
||||
{ label: 'Ny', value: 'ny' },
|
||||
{ label: 'Tn', value: 'tn' },
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FieldGroup>
|
||||
<Label
|
||||
:label-for="fieldNameAlias"
|
||||
:is-required="isRequired"
|
||||
>
|
||||
{{ labelForAlias }}
|
||||
</Label>
|
||||
|
||||
<Field
|
||||
:id="fieldNameAlias"
|
||||
:errors="errors"
|
||||
>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
:name="fieldNameAlias"
|
||||
>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Select
|
||||
:id="fieldNameAlias"
|
||||
:preserve-order="false"
|
||||
v-bind="componentField"
|
||||
:auto-width="true"
|
||||
:items="aliasOptions"
|
||||
:class="
|
||||
cn(
|
||||
'text-sm transition-all duration-200 focus:outline-none focus:ring-1 focus:ring-black focus:ring-offset-0',
|
||||
selectClass,
|
||||
)
|
||||
"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
<FieldGroup>
|
||||
<Label
|
||||
:label-for="fieldNameInput"
|
||||
@@ -0,0 +1,93 @@
|
||||
<script setup lang="ts">
|
||||
import type { FormErrors } from '~/types/error'
|
||||
import FieldGroup from '~/components/pub/my-ui/form/field-group.vue'
|
||||
import Field from '~/components/pub/my-ui/form/field.vue'
|
||||
import Label from '~/components/pub/my-ui/form/label.vue'
|
||||
import { Label as RadioLabel } from '~/components/pub/ui/label'
|
||||
import { RadioGroup, RadioGroupItem } from '~/components/pub/ui/radio-group'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
fieldName?: string
|
||||
label?: string
|
||||
errors?: FormErrors
|
||||
class?: string
|
||||
radioGroupClass?: string
|
||||
radioItemClass?: string
|
||||
labelClass?: string
|
||||
isRequired?: boolean
|
||||
}>()
|
||||
|
||||
const {
|
||||
fieldName = 'isNewBorn',
|
||||
label = 'Status Pasien',
|
||||
errors,
|
||||
class: containerClass,
|
||||
radioGroupClass,
|
||||
radioItemClass,
|
||||
labelClass,
|
||||
} = props
|
||||
|
||||
const newbornOptions = [
|
||||
{ label: 'Ya', value: 'YA' },
|
||||
{ label: 'Tidak', value: 'TIDAK' },
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FieldGroup :class="cn('radio-group-field', containerClass)">
|
||||
<Label
|
||||
:label-for="fieldName"
|
||||
:is-required="isRequired"
|
||||
>
|
||||
{{ label }}
|
||||
</Label>
|
||||
<Field
|
||||
:id="fieldName"
|
||||
:errors="errors"
|
||||
>
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
:name="fieldName"
|
||||
>
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<RadioGroup
|
||||
v-bind="componentField"
|
||||
:class="cn('flex flex-row flex-wrap gap-4 sm:gap-6', radioGroupClass)"
|
||||
>
|
||||
<div
|
||||
v-for="(option, index) in newbornOptions"
|
||||
:key="option.value"
|
||||
:class="cn('flex min-w-fit items-center space-x-2', radioItemClass)"
|
||||
>
|
||||
<RadioGroupItem
|
||||
:id="`${fieldName}-${index}`"
|
||||
:value="option.value"
|
||||
:class="
|
||||
cn(
|
||||
'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',
|
||||
containerClass,
|
||||
)
|
||||
"
|
||||
/>
|
||||
<RadioLabel
|
||||
:for="`${fieldName}-${index}`"
|
||||
:class="
|
||||
cn(
|
||||
'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',
|
||||
labelClass,
|
||||
)
|
||||
"
|
||||
>
|
||||
{{ option.label }}
|
||||
</RadioLabel>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
<FormMessage class="ml-0 mt-1" />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</template>
|
||||
@@ -4,11 +4,12 @@ import { toTypedSchema } from '@vee-validate/zod'
|
||||
import { Form } from '~/components/pub/ui/form'
|
||||
import InputBase from '~/components/pub/my-ui/form/input-base.vue'
|
||||
import InputFile from './_common/input-file.vue'
|
||||
import InputPatientName from './_common/input-patient-name.vue'
|
||||
import InputName from './_common/input-name.vue'
|
||||
import RadioCommunicationBarrier from './_common/radio-communication-barrier.vue'
|
||||
import RadioDisability from './_common/radio-disability.vue'
|
||||
import RadioGender from './_common/radio-gender.vue'
|
||||
import RadioNationality from './_common/radio-nationality.vue'
|
||||
import RadioNewborn from './_common/radio-newborn.vue'
|
||||
import SelectDisability from './_common/select-disability.vue'
|
||||
import SelectDob from './_common/select-dob.vue'
|
||||
import SelectEducation from './_common/select-education.vue'
|
||||
@@ -48,16 +49,21 @@ defineExpose({
|
||||
>
|
||||
<div class="mb-3 border-b border-b-slate-300">
|
||||
<p class="text-md mt-1 font-semibold">Data Diri Pasien</p>
|
||||
<div class="grid grid-cols-1 md:grid-cols-[150px_1fr]">
|
||||
<InputPatientName
|
||||
field-name-alias="alias"
|
||||
<div class="grid grid-cols-1 md:grid-cols-[1fr_1fr]">
|
||||
<InputName
|
||||
field-name-input="fullName"
|
||||
label-for-alias="Alias"
|
||||
label-for-input="Nama Lengkap"
|
||||
placeholder="Masukkan nama lengkap pasien"
|
||||
:errors="errors"
|
||||
is-required
|
||||
/>
|
||||
<RadioNewborn
|
||||
field-name="isNewBorn"
|
||||
label="Pasien Bayi"
|
||||
placeholder="Pilih status pasien"
|
||||
:errors="errors"
|
||||
is-required
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3">
|
||||
<InputBase
|
||||
|
||||
@@ -7,7 +7,8 @@ import Label from '~/components/pub/my-ui/form/label.vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
fieldName: string
|
||||
fieldName?: string
|
||||
regencyCode?: string
|
||||
isDisabled?: boolean
|
||||
placeholder?: string
|
||||
errors?: FormErrors
|
||||
@@ -17,13 +18,30 @@ const props = defineProps<{
|
||||
isRequired?: boolean
|
||||
}>()
|
||||
|
||||
const { placeholder = 'Pilih Kecamatan', errors, class: containerClass, selectClass, fieldGroupClass } = props
|
||||
const {
|
||||
fieldName = 'districtId',
|
||||
placeholder = 'Pilih kecamatan',
|
||||
errors,
|
||||
class: containerClass,
|
||||
fieldGroupClass,
|
||||
} = props
|
||||
|
||||
const districtOptions = [
|
||||
{ label: 'Kecamatan Lowokwaru', value: '18' },
|
||||
{ label: 'Kecamatan Pakis', value: '33' },
|
||||
{ label: 'Kecamatan Blimbing', value: '35' },
|
||||
]
|
||||
// Gunakan composable untuk mengelola data districts
|
||||
const regencyCodeRef = toRef(props, 'regencyCode')
|
||||
const { districtOptions, isLoading, error } = useDistricts(regencyCodeRef)
|
||||
|
||||
// Computed untuk menentukan placeholder berdasarkan state
|
||||
const dynamicPlaceholder = computed(() => {
|
||||
if (!props.regencyCode) return 'Pilih kabupaten/kota dahulu'
|
||||
if (isLoading.value) return 'Memuat data kecamatan...'
|
||||
if (error.value) return 'Gagal memuat data'
|
||||
return placeholder
|
||||
})
|
||||
|
||||
// Computed untuk menentukan apakah field disabled
|
||||
const isFieldDisabled = computed(() => {
|
||||
return props.isDisabled || !props.regencyCode || isLoading.value || !!error.value
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -49,9 +67,9 @@ const districtOptions = [
|
||||
:id="fieldName"
|
||||
v-bind="componentField"
|
||||
:items="districtOptions"
|
||||
:placeholder="placeholder"
|
||||
:is-disabled="isDisabled"
|
||||
search-placeholder="Cari..."
|
||||
:placeholder="dynamicPlaceholder"
|
||||
:is-disabled="isFieldDisabled"
|
||||
search-placeholder="Cari kecamatan..."
|
||||
empty-message="Kecamatan tidak ditemukan"
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
@@ -7,7 +7,7 @@ import Label from '~/components/pub/my-ui/form/label.vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
fieldName: string
|
||||
fieldName?: string
|
||||
isDisabled?: boolean
|
||||
placeholder?: string
|
||||
errors?: FormErrors
|
||||
@@ -18,18 +18,27 @@ const props = defineProps<{
|
||||
}>()
|
||||
|
||||
const {
|
||||
fieldName = 'provinceId',
|
||||
fieldName = 'provinceCode',
|
||||
placeholder = 'Pilih provinsi',
|
||||
errors,
|
||||
class: containerClass,
|
||||
fieldGroupClass,
|
||||
} = props
|
||||
|
||||
const provinceList = [
|
||||
{ label: 'Jawa Barat', value: '18' },
|
||||
{ label: 'Jawa Tengah', value: '33' },
|
||||
{ label: 'Jawa Timur', value: '35' },
|
||||
]
|
||||
// Gunakan composable untuk mengelola data provinces
|
||||
const { provinceOptions, isLoading, error } = useProvinces()
|
||||
|
||||
// Computed untuk menentukan placeholder berdasarkan state
|
||||
const dynamicPlaceholder = computed(() => {
|
||||
if (isLoading.value) return 'Memuat data provinsi...'
|
||||
if (error.value) return 'Gagal memuat data'
|
||||
return placeholder
|
||||
})
|
||||
|
||||
// Computed untuk menentukan apakah field disabled
|
||||
const isFieldDisabled = computed(() => {
|
||||
return props.isDisabled || isLoading.value || !!error.value
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -54,11 +63,11 @@ const provinceList = [
|
||||
<Combobox
|
||||
:id="fieldName"
|
||||
v-bind="componentField"
|
||||
:items="provinceList"
|
||||
:placeholder="placeholder"
|
||||
:is-disabled="isDisabled"
|
||||
search-placeholder="Cari..."
|
||||
empty-message="Kecamatan tidak ditemukan"
|
||||
:items="provinceOptions"
|
||||
:placeholder="dynamicPlaceholder"
|
||||
:is-disabled="isFieldDisabled"
|
||||
search-placeholder="Cari provinsi..."
|
||||
empty-message="Provinsi tidak ditemukan"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
|
||||
@@ -7,7 +7,8 @@ import Label from '~/components/pub/my-ui/form/label.vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
fieldName: string
|
||||
fieldName?: string
|
||||
provinceCode?: string
|
||||
isDisabled?: boolean
|
||||
placeholder?: string
|
||||
errors?: FormErrors
|
||||
@@ -17,15 +18,30 @@ const props = defineProps<{
|
||||
isRequired?: boolean
|
||||
}>()
|
||||
|
||||
const { placeholder = 'Pilih kabupaten/kota', errors, class: containerClass, selectClass, fieldGroupClass } = props
|
||||
const {
|
||||
fieldName = 'regencyId',
|
||||
placeholder = 'Pilih kabupaten/kota',
|
||||
errors,
|
||||
class: containerClass,
|
||||
fieldGroupClass,
|
||||
} = props
|
||||
|
||||
const regencyOptions = [
|
||||
{ label: 'Kab. Sidoarjo', value: '32' },
|
||||
{ label: 'Kab. Malang', value: '35' },
|
||||
{ label: 'Kab. Mojokerto', value: '31' },
|
||||
{ label: 'Kab. Lamongan', value: '30' },
|
||||
{ label: 'Kota Malang', value: '18' },
|
||||
]
|
||||
// Gunakan composable untuk mengelola data regencies
|
||||
const provinceCodeRef = toRef(props, 'provinceCode')
|
||||
const { regencyOptions, isLoading, error } = useRegencies(provinceCodeRef)
|
||||
|
||||
// Computed untuk menentukan placeholder berdasarkan state
|
||||
const dynamicPlaceholder = computed(() => {
|
||||
if (!props.provinceCode) return 'Pilih provinsi dahulu'
|
||||
if (isLoading.value) return 'Memuat data kabupaten/kota...'
|
||||
if (error.value) return 'Gagal memuat data'
|
||||
return placeholder
|
||||
})
|
||||
|
||||
// Computed untuk menentukan apakah field disabled
|
||||
const isFieldDisabled = computed(() => {
|
||||
return props.isDisabled || !props.provinceCode || isLoading.value || !!error.value
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -51,9 +67,9 @@ const regencyOptions = [
|
||||
:id="fieldName"
|
||||
v-bind="componentField"
|
||||
:items="regencyOptions"
|
||||
:placeholder="placeholder"
|
||||
:is-disabled="isDisabled"
|
||||
search-placeholder="Cari..."
|
||||
:placeholder="dynamicPlaceholder"
|
||||
:is-disabled="isFieldDisabled"
|
||||
search-placeholder="Cari kabupaten/kota..."
|
||||
empty-message="Kabupaten/kota tidak ditemukan"
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
@@ -7,7 +7,8 @@ import Label from '~/components/pub/my-ui/form/label.vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
fieldName: string
|
||||
fieldName?: string
|
||||
districtCode?: string
|
||||
isDisabled?: boolean
|
||||
placeholder?: string
|
||||
errors?: FormErrors
|
||||
@@ -17,14 +18,30 @@ const props = defineProps<{
|
||||
isRequired?: boolean
|
||||
}>()
|
||||
|
||||
const { placeholder = 'Pilih Kelurahan', errors, class: containerClass, selectClass, fieldGroupClass } = props
|
||||
const {
|
||||
fieldName = 'villageId',
|
||||
placeholder = 'Pilih kelurahan',
|
||||
errors,
|
||||
class: containerClass,
|
||||
fieldGroupClass,
|
||||
} = props
|
||||
|
||||
const villageOptions = [
|
||||
{ label: 'Lowokwaru', value: '18' },
|
||||
{ label: 'Dinoyo', value: '33' },
|
||||
{ label: 'Blimbing', value: '35' },
|
||||
{ label: 'Sawojajar', value: '36' },
|
||||
]
|
||||
// Gunakan composable untuk mengelola data villages
|
||||
const districtCodeRef = toRef(props, 'districtCode')
|
||||
const { villageOptions, isLoading, error } = useVillages(districtCodeRef)
|
||||
|
||||
// Computed untuk menentukan placeholder berdasarkan state
|
||||
const dynamicPlaceholder = computed(() => {
|
||||
if (!props.districtCode) return 'Pilih kecamatan dahulu'
|
||||
if (isLoading.value) return 'Memuat data kelurahan...'
|
||||
if (error.value) return 'Gagal memuat data'
|
||||
return placeholder
|
||||
})
|
||||
|
||||
// Computed untuk menentukan apakah field disabled
|
||||
const isFieldDisabled = computed(() => {
|
||||
return props.isDisabled || !props.districtCode || isLoading.value || !!error.value
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -50,9 +67,9 @@ const villageOptions = [
|
||||
:id="fieldName"
|
||||
v-bind="componentField"
|
||||
:items="villageOptions"
|
||||
:placeholder="placeholder"
|
||||
:is-disabled="isDisabled"
|
||||
search-placeholder="Cari..."
|
||||
:placeholder="dynamicPlaceholder"
|
||||
:is-disabled="isFieldDisabled"
|
||||
search-placeholder="Cari kelurahan..."
|
||||
empty-message="Kelurahan tidak ditemukan"
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
@@ -39,7 +39,7 @@ let isResetting = false
|
||||
|
||||
// Field dependency map for placeholder
|
||||
const fieldStates: Record<string, { dependsOn?: string; placeholder: string }> = {
|
||||
regencyId: { dependsOn: 'provinceId', placeholder: 'Pilih provinsi dahulu' },
|
||||
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' },
|
||||
@@ -84,9 +84,9 @@ function getFieldState(field: string) {
|
||||
|
||||
// #region watch
|
||||
|
||||
// Watch provinceId changes
|
||||
// Watch provinceCode changes
|
||||
watch(
|
||||
() => formRef.value?.values?.provinceId,
|
||||
() => formRef.value?.values?.provinceCode,
|
||||
(newValue, oldValue) => {
|
||||
if (isResetting || !formRef.value || newValue === oldValue) return
|
||||
|
||||
@@ -196,7 +196,7 @@ watch(
|
||||
const updatedValues = { ...currentValues }
|
||||
|
||||
// Convert empty strings to undefined untuk field yang sekarang required
|
||||
if (updatedValues.provinceId === '') updatedValues.provinceId = undefined
|
||||
if (updatedValues.provinceCode === '') updatedValues.provinceCode = undefined
|
||||
if (updatedValues.regencyId === '') updatedValues.regencyId = undefined
|
||||
if (updatedValues.districtId === '') updatedValues.districtId = undefined
|
||||
if (updatedValues.villageId === '') updatedValues.villageId = undefined
|
||||
@@ -217,7 +217,7 @@ watch(
|
||||
if (oldValue === '0' && newValue === '1') {
|
||||
nextTick(() => {
|
||||
// Clear error messages untuk field yang tidak lagi required
|
||||
formRef.value?.setFieldError('provinceId', undefined)
|
||||
formRef.value?.setFieldError('provinceCode', undefined)
|
||||
formRef.value?.setFieldError('regencyId', undefined)
|
||||
formRef.value?.setFieldError('districtId', undefined)
|
||||
formRef.value?.setFieldError('villageId', undefined)
|
||||
@@ -287,7 +287,7 @@ watch(
|
||||
<div class="flex-row gap-2 md:flex">
|
||||
<div class="min-w-0 flex-1">
|
||||
<SelectProvince
|
||||
field-name="provinceId"
|
||||
field-name="provinceCode"
|
||||
placeholder="Pilih"
|
||||
:is-disabled="values.isSameAddress === '1'"
|
||||
:is-required="values.isSameAddress !== '1'"
|
||||
@@ -297,8 +297,8 @@ watch(
|
||||
<div class="min-w-0 flex-1">
|
||||
<SelectRegency
|
||||
field-name="regencyId"
|
||||
:placeholder="getFieldState('regencyId').placeholder"
|
||||
:is-disabled="getFieldState('regencyId').disabled || !values.provinceId"
|
||||
:province-code="values.provinceCode"
|
||||
:is-disabled="getFieldState('regencyId').disabled"
|
||||
:is-required="values.isSameAddress !== '1'"
|
||||
/>
|
||||
</div>
|
||||
@@ -308,16 +308,16 @@ watch(
|
||||
<div class="min-w-0 flex-1">
|
||||
<SelectDistrict
|
||||
field-name="districtId"
|
||||
:placeholder="getFieldState('districtId').placeholder"
|
||||
:is-disabled="getFieldState('districtId').disabled || !values.regencyId"
|
||||
:regency-code="values.regencyId"
|
||||
:is-disabled="getFieldState('districtId').disabled"
|
||||
:is-required="values.isSameAddress !== '1'"
|
||||
/>
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<SelectVillage
|
||||
field-name="villageId"
|
||||
:placeholder="getFieldState('villageId').placeholder"
|
||||
:is-disabled="getFieldState('villageId').disabled || !values.districtId"
|
||||
:district-code="values.districtId"
|
||||
:is-disabled="getFieldState('villageId').disabled"
|
||||
:is-required="values.isSameAddress !== '1'"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -35,29 +35,34 @@ defineExpose({
|
||||
// Watchers untuk cascading reset
|
||||
let isResetting = false
|
||||
|
||||
// #region Watch provinceId changes
|
||||
// #region Watch provinceCode changes
|
||||
|
||||
watch(
|
||||
() => formRef.value?.values?.provinceId,
|
||||
() => formRef.value?.values?.provinceCode,
|
||||
(newValue, oldValue) => {
|
||||
if (isResetting || !formRef.value || newValue === oldValue) return
|
||||
|
||||
if (oldValue && newValue !== oldValue) {
|
||||
isResetting = true
|
||||
|
||||
formRef.value.setValues(
|
||||
{
|
||||
regencyId: undefined,
|
||||
districtId: undefined,
|
||||
villageId: undefined,
|
||||
zipCode: undefined,
|
||||
},
|
||||
false,
|
||||
)
|
||||
// Delay reset untuk memberikan waktu composable menyelesaikan request
|
||||
setTimeout(() => {
|
||||
if (formRef.value) {
|
||||
formRef.value.setValues(
|
||||
{
|
||||
regencyId: undefined,
|
||||
districtId: undefined,
|
||||
villageId: undefined,
|
||||
zipCode: undefined,
|
||||
},
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
nextTick(() => {
|
||||
isResetting = false
|
||||
})
|
||||
nextTick(() => {
|
||||
isResetting = false
|
||||
})
|
||||
}, 150) // Delay 150ms, lebih dari debounce composable (100ms)
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -71,18 +76,23 @@ watch(
|
||||
if (oldValue && newValue !== oldValue) {
|
||||
isResetting = true
|
||||
|
||||
formRef.value.setValues(
|
||||
{
|
||||
districtId: undefined,
|
||||
villageId: undefined,
|
||||
zipCode: undefined,
|
||||
},
|
||||
false,
|
||||
)
|
||||
// Delay reset untuk memberikan waktu composable menyelesaikan request
|
||||
setTimeout(() => {
|
||||
if (formRef.value) {
|
||||
formRef.value.setValues(
|
||||
{
|
||||
districtId: undefined,
|
||||
villageId: undefined,
|
||||
zipCode: undefined,
|
||||
},
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
nextTick(() => {
|
||||
isResetting = false
|
||||
})
|
||||
nextTick(() => {
|
||||
isResetting = false
|
||||
})
|
||||
}, 150)
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -96,17 +106,22 @@ watch(
|
||||
if (oldValue && newValue !== oldValue) {
|
||||
isResetting = true
|
||||
|
||||
formRef.value.setValues(
|
||||
{
|
||||
villageId: undefined,
|
||||
zipCode: undefined,
|
||||
},
|
||||
false,
|
||||
)
|
||||
// Delay reset untuk memberikan waktu composable menyelesaikan request
|
||||
setTimeout(() => {
|
||||
if (formRef.value) {
|
||||
formRef.value.setValues(
|
||||
{
|
||||
villageId: undefined,
|
||||
zipCode: undefined,
|
||||
},
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
nextTick(() => {
|
||||
isResetting = false
|
||||
})
|
||||
nextTick(() => {
|
||||
isResetting = false
|
||||
})
|
||||
}, 150)
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -189,7 +204,7 @@ watch(
|
||||
<div class="flex-row gap-2 md:flex">
|
||||
<div class="min-w-0 flex-1">
|
||||
<SelectProvince
|
||||
field-name="provinceId"
|
||||
field-name="provinceCode"
|
||||
placeholder="Pilih"
|
||||
is-required
|
||||
/>
|
||||
@@ -198,8 +213,7 @@ watch(
|
||||
<div class="min-w-0 flex-1">
|
||||
<SelectRegency
|
||||
field-name="regencyId"
|
||||
placeholder="Pilih provinsi dahulu"
|
||||
:is-disabled="!values.provinceId"
|
||||
:province-code="values.provinceCode"
|
||||
is-required
|
||||
/>
|
||||
</div>
|
||||
@@ -209,16 +223,14 @@ watch(
|
||||
<div class="min-w-0 flex-1">
|
||||
<SelectDistrict
|
||||
field-name="districtId"
|
||||
placeholder="Pilih kabupaten/kota dahulu"
|
||||
:is-disabled="!values.regencyId"
|
||||
:regency-code="values.regencyId"
|
||||
is-required
|
||||
/>
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<SelectVillage
|
||||
field-name="villageId"
|
||||
placeholder="Pilih kecamatan dahulu"
|
||||
:is-disabled="!values.districtId"
|
||||
:district-code="values.districtId"
|
||||
is-required
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -94,7 +94,7 @@ watch(
|
||||
personAddressRelativeForm.value.setValues(
|
||||
{
|
||||
...personAddressRelativeForm.value.values,
|
||||
provinceId: newAddressValues.provinceId || '',
|
||||
provinceCode: newAddressValues.provinceCode || '',
|
||||
regencyId: newAddressValues.regencyId || '',
|
||||
districtId: newAddressValues.districtId || '',
|
||||
villageId: newAddressValues.villageId || '',
|
||||
@@ -120,7 +120,7 @@ watch(
|
||||
personAddressRelativeForm.value.setValues(
|
||||
{
|
||||
...personAddressRelativeForm.value.values,
|
||||
provinceId: currentAddressValues.provinceId || '',
|
||||
provinceCode: currentAddressValues.provinceCode || '',
|
||||
regencyId: currentAddressValues.regencyId || '',
|
||||
districtId: currentAddressValues.districtId || '',
|
||||
villageId: currentAddressValues.villageId || '',
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import type { SelectItem } from '~/components/pub/my-ui/form/select.vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
interface Item {
|
||||
value: string
|
||||
label: string
|
||||
code?: string
|
||||
priority?: number
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
id: string
|
||||
modelValue?: string
|
||||
items: Item[]
|
||||
items: SelectItem[]
|
||||
placeholder?: string
|
||||
searchPlaceholder?: string
|
||||
emptyMessage?: string
|
||||
@@ -55,7 +49,7 @@ const searchableItems = computed(() => {
|
||||
})
|
||||
})
|
||||
|
||||
function onSelect(item: Item) {
|
||||
function onSelect(item: SelectItem) {
|
||||
emit('update:modelValue', item.value)
|
||||
open.value = false
|
||||
}
|
||||
@@ -74,10 +68,11 @@ function onSelect(item: Item) {
|
||||
:aria-describedby="`${props.id}-search`"
|
||||
:class="
|
||||
cn(
|
||||
'w-full justify-between border text-sm font-normal rounded-md px-3 py-2 focus:outline-none focus:ring-1 focus:ring-black dark:focus:ring-white',
|
||||
'w-full justify-between rounded-md border px-3 py-2 text-sm font-normal focus:outline-none focus:ring-1 focus:ring-black dark:focus:ring-white',
|
||||
{
|
||||
'cursor-not-allowed bg-gray-100 opacity-50 border-gray-300 text-gray-500': props.isDisabled,
|
||||
'bg-white text-black dark:bg-gray-800 dark:text-white dark:border-gray-600 border-gray-400 hover:bg-gray-50 dark:hover:bg-gray-700': !props.isDisabled,
|
||||
'cursor-not-allowed border-gray-300 bg-gray-100 text-gray-500 opacity-50': props.isDisabled,
|
||||
'border-gray-400 bg-white text-black hover:bg-gray-50 dark:border-gray-600 dark:bg-gray-800 dark:text-white dark:hover:bg-gray-700':
|
||||
!props.isDisabled,
|
||||
'text-gray-400 dark:text-gray-500': !modelValue && !props.isDisabled,
|
||||
},
|
||||
props.class,
|
||||
@@ -87,22 +82,26 @@ function onSelect(item: Item) {
|
||||
{{ displayText }}
|
||||
<Icon
|
||||
name="i-lucide-chevrons-up-down"
|
||||
:class="cn('ml-2 h-4 w-4 shrink-0', {
|
||||
'opacity-30': props.isDisabled,
|
||||
'opacity-50 text-gray-500 dark:text-gray-300': !props.isDisabled
|
||||
})"
|
||||
:class="
|
||||
cn('ml-2 h-4 w-4 shrink-0', {
|
||||
'opacity-30': props.isDisabled,
|
||||
'text-gray-500 opacity-50 dark:text-gray-300': !props.isDisabled,
|
||||
})
|
||||
"
|
||||
/>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="w-[var(--radix-popover-trigger-width)] p-0 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700">
|
||||
<PopoverContent
|
||||
class="w-[var(--radix-popover-trigger-width)] border border-gray-200 bg-white p-0 dark:border-gray-700 dark:bg-gray-800"
|
||||
>
|
||||
<Command class="bg-white dark:bg-gray-800">
|
||||
<CommandInput
|
||||
:id="`${props.id}-search`"
|
||||
class="h-9 bg-white dark:bg-gray-800 text-black dark:text-white border-0 border-b border-gray-200 dark:border-gray-700 focus:ring-0"
|
||||
class="h-9 border-0 border-b border-gray-200 bg-white text-black focus:ring-0 dark:border-gray-700 dark:bg-gray-800 dark:text-white"
|
||||
:placeholder="searchPlaceholder || 'Cari...'"
|
||||
:aria-label="`Cari ${displayText}`"
|
||||
/>
|
||||
<CommandEmpty class="text-gray-500 dark:text-gray-400 py-6 text-center text-sm">
|
||||
<CommandEmpty class="py-6 text-center text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ emptyMessage || 'Item tidak ditemukan.' }}
|
||||
</CommandEmpty>
|
||||
<CommandList
|
||||
@@ -118,7 +117,7 @@ function onSelect(item: Item) {
|
||||
:class="
|
||||
cn(
|
||||
'flex w-full cursor-pointer items-center justify-between rounded-sm px-2 py-1.5 text-sm',
|
||||
'focus:outline-none text-black dark:text-white',
|
||||
'text-black focus:outline-none dark:text-white',
|
||||
'hover:bg-primary hover:text-white focus:bg-primary focus:text-white',
|
||||
'data-[highlighted]:bg-primary data-[highlighted]:text-white',
|
||||
)
|
||||
|
||||
@@ -10,7 +10,7 @@ import SelectTrigger from '~/components/pub/ui/select/SelectTrigger.vue'
|
||||
import SelectValue from '~/components/pub/ui/select/SelectValue.vue'
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
interface Item {
|
||||
export interface SelectItem {
|
||||
value: string
|
||||
label: string
|
||||
code?: string
|
||||
@@ -19,7 +19,7 @@ interface Item {
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue?: string
|
||||
items: Item[]
|
||||
items: SelectItem[]
|
||||
placeholder?: string
|
||||
label?: string
|
||||
separator?: boolean
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
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),
|
||||
})
|
||||
@@ -0,0 +1,113 @@
|
||||
import { ref, computed } from 'vue'
|
||||
import type { Province } from '~/models/province'
|
||||
import type { SelectItem } from '~/components/pub/my-ui/form/select.vue'
|
||||
import { toTitleCase } from '~/lib/utils'
|
||||
import * as provinceService from '~/services/province.service'
|
||||
|
||||
// Global state untuk caching
|
||||
const provincesCache = ref<Province[]>([])
|
||||
const isLoading = ref(false)
|
||||
const isInitialized = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
export function useProvinces() {
|
||||
// Computed untuk format SelectItem
|
||||
const provinceOptions = computed<SelectItem[]>(() => {
|
||||
return provincesCache.value.map(province => ({
|
||||
label: toTitleCase(province.name),
|
||||
value: province.code,
|
||||
// code: province.code,
|
||||
searchValue: `${province.code} ${province.name}`.trim() // Untuk search internal combobox
|
||||
}))
|
||||
})
|
||||
|
||||
// Function untuk fetch data provinces
|
||||
async function fetchProvinces(forceRefresh = false) {
|
||||
// Jika sudah ada data dan tidak force refresh, skip
|
||||
if (isInitialized.value && !forceRefresh) {
|
||||
return
|
||||
}
|
||||
|
||||
// Jika sedang loading, skip untuk mencegah duplicate calls
|
||||
if (isLoading.value) {
|
||||
return
|
||||
}
|
||||
|
||||
isLoading.value = true
|
||||
error.value = null
|
||||
|
||||
try {
|
||||
const response = await provinceService.getList({
|
||||
'page-no-limit': '1',
|
||||
'sort': 'name:asc'
|
||||
})
|
||||
|
||||
if (response.success) {
|
||||
provincesCache.value = response.body.data || []
|
||||
isInitialized.value = true
|
||||
} else {
|
||||
error.value = 'Gagal memuat data provinsi'
|
||||
console.error('Failed to fetch provinces:', response)
|
||||
}
|
||||
} catch (err) {
|
||||
error.value = 'Terjadi kesalahan saat memuat data provinsi'
|
||||
console.error('Error fetching provinces:', err)
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// Function untuk mencari province berdasarkan code
|
||||
function getProvinceByCode(code: string): Province | undefined {
|
||||
return provincesCache.value.find(province => province.code === code)
|
||||
}
|
||||
|
||||
// Function untuk mencari province berdasarkan name
|
||||
function getProvinceByName(name: string): Province | undefined {
|
||||
return provincesCache.value.find(province =>
|
||||
province.name.toLowerCase() === name.toLowerCase()
|
||||
)
|
||||
}
|
||||
|
||||
// Function untuk clear cache (jika diperlukan)
|
||||
function clearCache() {
|
||||
provincesCache.value = []
|
||||
isInitialized.value = false
|
||||
error.value = null
|
||||
}
|
||||
|
||||
// Function untuk refresh data
|
||||
function refreshProvinces() {
|
||||
return fetchProvinces(true)
|
||||
}
|
||||
|
||||
// Auto fetch saat composable pertama kali digunakan
|
||||
if (!isInitialized.value && !isLoading.value) {
|
||||
fetchProvinces()
|
||||
}
|
||||
|
||||
return {
|
||||
// Data
|
||||
provinces: readonly(provincesCache),
|
||||
provinceOptions,
|
||||
|
||||
// State
|
||||
isLoading: readonly(isLoading),
|
||||
isInitialized: readonly(isInitialized),
|
||||
error: readonly(error),
|
||||
|
||||
// Methods
|
||||
fetchProvinces,
|
||||
refreshProvinces,
|
||||
getProvinceByCode,
|
||||
getProvinceByName,
|
||||
clearCache,
|
||||
}
|
||||
}
|
||||
|
||||
// Export untuk direct access ke cached data (jika diperlukan)
|
||||
export const useProvincesCache = () => ({
|
||||
provinces: readonly(provincesCache),
|
||||
isLoading: readonly(isLoading),
|
||||
isInitialized: readonly(isInitialized),
|
||||
})
|
||||
@@ -0,0 +1,181 @@
|
||||
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),
|
||||
})
|
||||
@@ -0,0 +1,181 @@
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { refDebounced } from '@vueuse/core'
|
||||
import type { Village } from '~/models/village'
|
||||
import type { SelectItem } from '~/components/pub/my-ui/form/select.vue'
|
||||
import { toTitleCase } from '~/lib/utils'
|
||||
import * as villageService from '~/services/village.service'
|
||||
|
||||
// Global cache untuk villages berdasarkan district code
|
||||
const villagesCache = ref<Map<string, Village[]>>(new Map())
|
||||
const loadingStates = ref<Map<string, boolean>>(new Map())
|
||||
const errorStates = ref<Map<string, string | null>>(new Map())
|
||||
|
||||
export function useVillages(districtCode: Ref<string | undefined> | string | undefined) {
|
||||
// Convert districtCode ke ref jika bukan ref
|
||||
const districtCodeRef =
|
||||
typeof districtCode === 'string' || districtCode === undefined ? ref(districtCode) : districtCode
|
||||
|
||||
// Computed untuk mendapatkan villages berdasarkan district code
|
||||
const villages = computed(() => {
|
||||
const code = districtCodeRef.value
|
||||
if (!code) return []
|
||||
return villagesCache.value.get(code) || []
|
||||
})
|
||||
|
||||
// Computed untuk loading state
|
||||
const isLoading = computed(() => {
|
||||
const code = districtCodeRef.value
|
||||
if (!code) return false
|
||||
return loadingStates.value.get(code) || false
|
||||
})
|
||||
|
||||
// Computed untuk error state
|
||||
const error = computed(() => {
|
||||
const code = districtCodeRef.value
|
||||
if (!code) return null
|
||||
return errorStates.value.get(code) || null
|
||||
})
|
||||
|
||||
// Computed untuk format SelectItem
|
||||
const villageOptions = computed<SelectItem[]>(() => {
|
||||
return villages.value.map((village) => ({
|
||||
label: toTitleCase(village.name),
|
||||
value: village.code,
|
||||
searchValue: `${village.code} ${village.name}`.trim(),
|
||||
}))
|
||||
})
|
||||
|
||||
// Function untuk fetch villages berdasarkan district code
|
||||
async function fetchVillages(districtCodeParam?: string, forceRefresh = false, isUserAction = false) {
|
||||
const code = districtCodeParam || districtCodeRef.value
|
||||
if (!code) return
|
||||
|
||||
// Jika user action atau force refresh, selalu fetch
|
||||
// Jika bukan user action dan sudah ada cache, skip
|
||||
if (!isUserAction && !forceRefresh && villagesCache.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 villageService.getList({
|
||||
'page-size': '50',
|
||||
sort: 'name:asc',
|
||||
district_code: code,
|
||||
})
|
||||
|
||||
if (response.success) {
|
||||
const villagesData = response.body.data || []
|
||||
villagesCache.value.set(code, villagesData)
|
||||
} else {
|
||||
errorStates.value.set(code, 'Gagal memuat data kelurahan')
|
||||
console.error('Failed to fetch villages:', response)
|
||||
}
|
||||
} catch (err) {
|
||||
errorStates.value.set(code, 'Terjadi kesalahan saat memuat data kelurahan')
|
||||
console.error('Error fetching villages:', err)
|
||||
} finally {
|
||||
loadingStates.value.set(code, false)
|
||||
loadingStates.value.delete(pendingKey)
|
||||
}
|
||||
}
|
||||
|
||||
// Function untuk mencari village berdasarkan code
|
||||
function getVillageByCode(code: string): Village | undefined {
|
||||
const districtCode = districtCodeRef.value
|
||||
if (!districtCode) return undefined
|
||||
|
||||
const villagesForDistrict = villagesCache.value.get(districtCode) || []
|
||||
return villagesForDistrict.find((village) => village.code === code)
|
||||
}
|
||||
|
||||
// Function untuk mencari village berdasarkan name
|
||||
function getVillageByName(name: string): Village | undefined {
|
||||
const districtCode = districtCodeRef.value
|
||||
if (!districtCode) return undefined
|
||||
|
||||
const villagesForDistrict = villagesCache.value.get(districtCode) || []
|
||||
return villagesForDistrict.find((village) => village.name.toLowerCase() === name.toLowerCase())
|
||||
}
|
||||
|
||||
// Function untuk clear cache district tertentu
|
||||
function clearCache(districtCodeParam?: string) {
|
||||
const code = districtCodeParam || districtCodeRef.value
|
||||
if (code) {
|
||||
villagesCache.value.delete(code)
|
||||
loadingStates.value.delete(code)
|
||||
errorStates.value.delete(code)
|
||||
}
|
||||
}
|
||||
|
||||
// Function untuk clear semua cache
|
||||
function clearAllCache() {
|
||||
villagesCache.value.clear()
|
||||
loadingStates.value.clear()
|
||||
errorStates.value.clear()
|
||||
}
|
||||
|
||||
// Function untuk refresh data
|
||||
function refreshVillages(districtCodeParam?: string) {
|
||||
const code = districtCodeParam || districtCodeRef.value
|
||||
if (code) {
|
||||
return fetchVillages(code, true)
|
||||
}
|
||||
}
|
||||
|
||||
// Debounced district code untuk mencegah multiple calls
|
||||
const debouncedDistrictCode = refDebounced(districtCodeRef, 100)
|
||||
|
||||
// Watch perubahan district code untuk auto fetch
|
||||
watch(
|
||||
debouncedDistrictCode,
|
||||
(newCode, oldCode) => {
|
||||
if (newCode && newCode !== oldCode) {
|
||||
// Jika ada oldCode berarti user action (ganti pilihan)
|
||||
const isUserAction = !!oldCode
|
||||
fetchVillages(newCode, false, isUserAction)
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
return {
|
||||
// Data
|
||||
villages: readonly(villages),
|
||||
villageOptions,
|
||||
|
||||
// State
|
||||
isLoading: readonly(isLoading),
|
||||
error: readonly(error),
|
||||
|
||||
// Methods
|
||||
fetchVillages,
|
||||
refreshVillages,
|
||||
getVillageByCode,
|
||||
getVillageByName,
|
||||
clearCache,
|
||||
clearAllCache,
|
||||
}
|
||||
}
|
||||
|
||||
// Export untuk direct access ke cached data (jika diperlukan)
|
||||
export const useVillagesCache = () => ({
|
||||
villagesCache: readonly(villagesCache),
|
||||
loadingStates: readonly(loadingStates),
|
||||
errorStates: readonly(errorStates),
|
||||
})
|
||||
@@ -27,6 +27,15 @@ export function mapToComboboxOptList(items: Record<string, string>): SelectOptio
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Mengkonversi string menjadi title case (huruf pertama setiap kata kapital)
|
||||
* @param str - String yang akan dikonversi
|
||||
* @returns String dalam format title case
|
||||
*/
|
||||
export function toTitleCase(str: string): string {
|
||||
return str.toLowerCase().replace(/\b\w/g, (char) => char.toUpperCase())
|
||||
}
|
||||
|
||||
/**
|
||||
* Menghitung umur berdasarkan tanggal lahir
|
||||
* @param birthDate - Tanggal lahir dalam format Date atau string
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { type Base, genBase } from './_base'
|
||||
|
||||
export interface District extends Base {
|
||||
regency_code: string
|
||||
code: string
|
||||
name: string
|
||||
}
|
||||
|
||||
export function genDistrict(): District {
|
||||
return {
|
||||
...genBase(),
|
||||
regency_code: '',
|
||||
name: '',
|
||||
code: '',
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,7 @@ export function genPatient(props: genPatientProps): PatientEntity {
|
||||
const personContacts: PersonContact[] = []
|
||||
|
||||
// jika alamat ktp sama dengan domisili saat ini
|
||||
if (cardAddress.isSameAddress === '1') {
|
||||
if (cardAddress.isSameAddress) {
|
||||
addresses.push({ ...genBase(), person_id: 0, locationType: '', ...residentAddress })
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ export function genPatient(props: genPatientProps): PatientEntity {
|
||||
person: {
|
||||
id: 0,
|
||||
name: patient.fullName,
|
||||
alias: patient.alias,
|
||||
// alias: patient.alias,
|
||||
birthDate: patient.birthDate,
|
||||
birthRegency_code: patient.birthPlace,
|
||||
gender_code: patient.gender,
|
||||
@@ -111,7 +111,7 @@ export function genPatient(props: genPatientProps): PatientEntity {
|
||||
ethnic_code: patient.ethnicity,
|
||||
language_code: patient.language,
|
||||
communicationIssueStatus: patient.communicationBarrier,
|
||||
disability: patient.disability,
|
||||
disability: patient.disabilityType || '',
|
||||
nationality: patient.nationality,
|
||||
// residentIdentityFileUrl: patient.residentIdentityFileUrl,
|
||||
// passportFileUrl: patient.passportFileUrl,
|
||||
@@ -126,7 +126,7 @@ export function genPatient(props: genPatientProps): PatientEntity {
|
||||
personRelatives: familiesContact,
|
||||
registeredAt: new Date(),
|
||||
status_code: 'active',
|
||||
newBornStatus: false,
|
||||
newBornStatus: patient.isNewBorn,
|
||||
person_id: 0,
|
||||
id: 0,
|
||||
number: '0x000000000000000000000000000000',
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { type Base, genBase } from "./_base"
|
||||
import { type Base, genBase } from './_base'
|
||||
|
||||
export interface Person extends Base {
|
||||
// todo: awaiting approve from stake holder: buat field sapaan
|
||||
// todo: adjust field ketika person Balita
|
||||
name: string
|
||||
alias?: string
|
||||
// alias?: string
|
||||
frontTitle?: string
|
||||
endTitle?: string
|
||||
birthDate?: Date | string
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { type Base, genBase } from './_base'
|
||||
|
||||
export interface Village extends Base {
|
||||
district_code: string
|
||||
code: string
|
||||
name: string
|
||||
}
|
||||
|
||||
export function genVillage(): Village {
|
||||
return {
|
||||
...genBase(),
|
||||
district_code: '',
|
||||
code: '',
|
||||
name: '',
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,12 @@ const CommunicationBarrierSchema = z
|
||||
})
|
||||
.transform((val) => val === 'YA')
|
||||
|
||||
const IsNewBornSchema = z
|
||||
.enum(['YA', 'TIDAK'], {
|
||||
required_error: 'Mohon lengkapi status pasien',
|
||||
})
|
||||
.transform((val) => val === 'YA')
|
||||
|
||||
const PatientSchema = z
|
||||
.object({
|
||||
// Data Diri Pasien
|
||||
@@ -19,9 +25,9 @@ const PatientSchema = z
|
||||
// familyCardFile: z.instanceof(File, { message: 'File KK harus dipilih' }),
|
||||
|
||||
// Informasi Dasar
|
||||
alias: z.string({
|
||||
required_error: 'Mohon pilih sapaan',
|
||||
}),
|
||||
// alias: z.string({
|
||||
// required_error: 'Mohon pilih sapaan',
|
||||
// }),
|
||||
fullName: z.string({
|
||||
required_error: 'Mohon lengkapi Nama',
|
||||
}),
|
||||
@@ -71,6 +77,7 @@ const PatientSchema = z
|
||||
nationality: z.string({
|
||||
required_error: 'Pilih Kebangsaan',
|
||||
}),
|
||||
isNewBorn: IsNewBornSchema,
|
||||
language: z.string({
|
||||
required_error: 'Mohon pilih Preferensi Bahasa',
|
||||
}),
|
||||
@@ -99,14 +106,37 @@ const PatientSchema = z
|
||||
note: z.string().optional(),
|
||||
drivingLicenseNumber: z.string().optional(),
|
||||
})
|
||||
.refine((data) => (data.disability === 'TIDAK' ? !data.disabilityType : true), {
|
||||
message: "DisabilityType hanya boleh diisi jika disability = 'YA'",
|
||||
.refine((data) => {
|
||||
// Jika disability = 'TIDAK', maka disabilityType harus kosong atau undefined
|
||||
if (data.disability === 'TIDAK') {
|
||||
return !data.disabilityType || data.disabilityType.trim() === ''
|
||||
}
|
||||
return true
|
||||
}, {
|
||||
message: "Jenis Disabilitas harus kosong jika Status Disabilitas = 'TIDAK'",
|
||||
path: ['disabilityType'],
|
||||
})
|
||||
.refine((data) => (data.disability === 'YA' ? !!data.disabilityType?.trim() : true), {
|
||||
.refine((data) => {
|
||||
// Jika disability = 'YA', maka disabilityType wajib diisi
|
||||
if (data.disability === 'YA') {
|
||||
return !!data.disabilityType?.trim()
|
||||
}
|
||||
return true
|
||||
}, {
|
||||
message: 'Mohon pilih Jenis Disabilitas',
|
||||
path: ['disabilityType'],
|
||||
})
|
||||
.transform((data) => {
|
||||
// Transform untuk backend: hanya kirim disabilityType sesuai kondisi
|
||||
return {
|
||||
...data,
|
||||
// Jika disability = 'YA', kirim disabilityType
|
||||
// Jika disability = 'TIDAK', kirim null untuk disabilityType
|
||||
disabilityType: data.disability === 'YA' ? data.disabilityType : null,
|
||||
// Hapus field disability karena yang dikirim ke backend adalah disabilityType
|
||||
disability: undefined,
|
||||
}
|
||||
})
|
||||
|
||||
type PatientFormData = z.infer<typeof PatientSchema>
|
||||
|
||||
|
||||
@@ -7,19 +7,24 @@ 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),
|
||||
const PersonAddressRelativeSchema = z
|
||||
.discriminatedUnion('isSameAddress', [
|
||||
z
|
||||
.object({
|
||||
isSameAddress: z.literal('1').default('1'),
|
||||
})
|
||||
.merge(OptionalAddressSchema),
|
||||
|
||||
z
|
||||
.object({
|
||||
isSameAddress: z.literal('0'),
|
||||
})
|
||||
.merge(RequiredAddressSchema),
|
||||
])
|
||||
z
|
||||
.object({
|
||||
isSameAddress: z.literal('0'),
|
||||
})
|
||||
.merge(RequiredAddressSchema),
|
||||
])
|
||||
.transform((data) => ({
|
||||
...data,
|
||||
isSameAddress: data.isSameAddress === '1',
|
||||
}))
|
||||
|
||||
type PersonAddressRelativeFormData = z.infer<typeof PersonAddressRelativeSchema>
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
// Base
|
||||
import * as base from './_crud-base'
|
||||
|
||||
const path = '/api/v1/district'
|
||||
const name = 'district'
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
export function update(id: number | string, data: any) {
|
||||
return base.update(path, id, data, name)
|
||||
}
|
||||
|
||||
export function remove(id: number | string) {
|
||||
return base.remove(path, id, name)
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// Base
|
||||
import * as base from './_crud-base'
|
||||
|
||||
const path = '/api/v1/province'
|
||||
const name = 'province'
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
export function update(id: number | string, data: any) {
|
||||
return base.update(path, id, data, name)
|
||||
}
|
||||
|
||||
export function remove(id: number | string) {
|
||||
return base.remove(path, id, name)
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// Base
|
||||
import * as base from './_crud-base'
|
||||
|
||||
const path = '/api/v1/regency'
|
||||
const name = 'regency'
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
export function update(id: number | string, data: any) {
|
||||
return base.update(path, id, data, name)
|
||||
}
|
||||
|
||||
export function remove(id: number | string) {
|
||||
return base.remove(path, id, name)
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// Base
|
||||
import * as base from './_crud-base'
|
||||
|
||||
const path = '/api/v1/village'
|
||||
const name = 'village'
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
export function update(id: number | string, data: any) {
|
||||
return base.update(path, id, data, name)
|
||||
}
|
||||
|
||||
export function remove(id: number | string) {
|
||||
return base.remove(path, id, name)
|
||||
}
|
||||
Reference in New Issue
Block a user