-
-
Data Pasien
-
-
-
- Sudah pernah terdaftar sebagai pasien?
-
-
-
- Belum pernah terdaftar sebagai pasien?
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- |
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- |
-
-
-
-
-
-
-
-
-
-
-
-
- |
-
-
-
-
-
Data Kunjungan
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- |
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- |
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- |
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- |
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- |
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- |
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- |
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- |
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- |
-
+
diff --git a/app/components/app/patient/_common/input-name.vue b/app/components/app/patient/_common/input-name.vue
new file mode 100644
index 00000000..db9005f1
--- /dev/null
+++ b/app/components/app/patient/_common/input-name.vue
@@ -0,0 +1,54 @@
+
+
+
+
+
+ {{ labelForInput }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/components/app/patient/_common/input-patient-name.vue b/app/components/app/patient/_common/input-patient-name.vue
deleted file mode 100644
index fbc57351..00000000
--- a/app/components/app/patient/_common/input-patient-name.vue
+++ /dev/null
@@ -1,102 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/components/app/patient/_common/radio-communication-barrier.vue b/app/components/app/patient/_common/radio-communication-barrier.vue
index 463b3659..56918101 100644
--- a/app/components/app/patient/_common/radio-communication-barrier.vue
+++ b/app/components/app/patient/_common/radio-communication-barrier.vue
@@ -1,12 +1,11 @@
-
-
-
+
+ class="pt-0.5"
+ >
-
-
+
+
diff --git a/app/components/app/patient/_common/radio-disability.vue b/app/components/app/patient/_common/radio-disability.vue
index 312c3730..6c1464e2 100644
--- a/app/components/app/patient/_common/radio-disability.vue
+++ b/app/components/app/patient/_common/radio-disability.vue
@@ -1,12 +1,11 @@
-
-
-
+
-
-
+
+
diff --git a/app/components/app/patient/_common/radio-gender.vue b/app/components/app/patient/_common/radio-gender.vue
index 108b1d22..47968c8f 100644
--- a/app/components/app/patient/_common/radio-gender.vue
+++ b/app/components/app/patient/_common/radio-gender.vue
@@ -1,13 +1,12 @@
-
-
-
+
@@ -65,7 +64,7 @@ const genderOptions = mapToComboboxOptList(genderCodes)
: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',
+ 'relative h-4 w-4 rounded-full border-1 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,
)
"
@@ -74,7 +73,7 @@ const genderOptions = mapToComboboxOptList(genderCodes)
: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',
+ 'cursor-pointer select-none text-xs 2xl:text-sm leading-none transition-colors hover:text-primary peer-disabled:cursor-not-allowed peer-disabled:opacity-70 !font-normal',
labelClass,
)
"
@@ -87,6 +86,6 @@ const genderOptions = mapToComboboxOptList(genderCodes)
-
-
+
+
diff --git a/app/components/app/patient/_common/radio-nationality.vue b/app/components/app/patient/_common/radio-nationality.vue
index 9c0eef1d..40525dff 100644
--- a/app/components/app/patient/_common/radio-nationality.vue
+++ b/app/components/app/patient/_common/radio-nationality.vue
@@ -1,12 +1,11 @@
-
-
-
+
-
-
+
+
diff --git a/app/components/app/patient/_common/radio-newborn.vue b/app/components/app/patient/_common/radio-newborn.vue
new file mode 100644
index 00000000..86953443
--- /dev/null
+++ b/app/components/app/patient/_common/radio-newborn.vue
@@ -0,0 +1,92 @@
+
+
+
+
+
+ {{ label }}
+
+
+
+
+
+
+
+
+
+ {{ option.label }}
+
+
+
+
+
+
+
+
+
+
diff --git a/app/components/app/patient/_common/select-disability.vue b/app/components/app/patient/_common/select-disability.vue
index de65a07f..6058773a 100644
--- a/app/components/app/patient/_common/select-disability.vue
+++ b/app/components/app/patient/_common/select-disability.vue
@@ -1,11 +1,10 @@
-
-
-
+
-
-
+
+
diff --git a/app/components/app/patient/_common/select-dob.vue b/app/components/app/patient/_common/select-dob.vue
index 938bd254..eee11acc 100644
--- a/app/components/app/patient/_common/select-dob.vue
+++ b/app/components/app/patient/_common/select-dob.vue
@@ -1,12 +1,11 @@
-
-
-
+
-
-
-
-
-
-
+
+
+
+ Usia
+
@@ -137,6 +135,6 @@ function calculateAge(birthDate: string | Date | undefined): string {
-
-
+
+
diff --git a/app/components/app/patient/_common/select-education.vue b/app/components/app/patient/_common/select-education.vue
index 33caac16..848ed614 100644
--- a/app/components/app/patient/_common/select-education.vue
+++ b/app/components/app/patient/_common/select-education.vue
@@ -1,12 +1,11 @@
-
-
-
+
-
-
+
+
diff --git a/app/components/app/patient/_common/select-ethnicity.vue b/app/components/app/patient/_common/select-ethnicity.vue
index e1ecd08a..43ef6cba 100644
--- a/app/components/app/patient/_common/select-ethnicity.vue
+++ b/app/components/app/patient/_common/select-ethnicity.vue
@@ -1,11 +1,10 @@
-
-
-
+
-
-
+
+
diff --git a/app/components/app/patient/_common/select-gender.vue b/app/components/app/patient/_common/select-gender.vue
new file mode 100644
index 00000000..dfedf9ec
--- /dev/null
+++ b/app/components/app/patient/_common/select-gender.vue
@@ -0,0 +1,76 @@
+
+
+
+
+
+ {{ label }}
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/components/app/patient/_common/select-job.vue b/app/components/app/patient/_common/select-job.vue
index b14cbb66..814eb7ca 100644
--- a/app/components/app/patient/_common/select-job.vue
+++ b/app/components/app/patient/_common/select-job.vue
@@ -1,10 +1,10 @@
-
-
-
+
-
-
+
+
diff --git a/app/components/app/patient/_common/select-lang.vue b/app/components/app/patient/_common/select-lang.vue
index aadf9313..ce5bbac8 100644
--- a/app/components/app/patient/_common/select-lang.vue
+++ b/app/components/app/patient/_common/select-lang.vue
@@ -1,11 +1,10 @@
-
-
-
+
-
-
+
+
diff --git a/app/components/app/patient/_common/select-marital-status.vue b/app/components/app/patient/_common/select-marital-status.vue
index cb3b56fb..21ebf0c2 100644
--- a/app/components/app/patient/_common/select-marital-status.vue
+++ b/app/components/app/patient/_common/select-marital-status.vue
@@ -1,11 +1,10 @@
-
-
-
+
-
-
+
+
diff --git a/app/components/app/patient/_common/select-religion.vue b/app/components/app/patient/_common/select-religion.vue
index 069e3575..6a1fec66 100644
--- a/app/components/app/patient/_common/select-religion.vue
+++ b/app/components/app/patient/_common/select-religion.vue
@@ -1,12 +1,11 @@
-
-
-
+
-
-
+
+
diff --git a/app/components/app/patient/entry-form.vue b/app/components/app/patient/entry-form.vue
index e3587988..a83189c2 100644
--- a/app/components/app/patient/entry-form.vue
+++ b/app/components/app/patient/entry-form.vue
@@ -3,12 +3,14 @@ import type { FormErrors } from '~/types/error'
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 FileUpload from '~/components/pub/my-ui/form/file-field.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 SelectGender from './_common/select-gender.vue'
import RadioNationality from './_common/radio-nationality.vue'
+import RadioNewborn from './_common/radio-newborn.vue'
+import SelectBirthPlace from '~/components/app/person/_common/select-birth-place.vue'
import SelectDisability from './_common/select-disability.vue'
import SelectDob from './_common/select-dob.vue'
import SelectEducation from './_common/select-education.vue'
@@ -17,6 +19,9 @@ import SelectJob from './_common/select-job.vue'
import SelectLanguage from './_common/select-lang.vue'
import SelectMaritalStatus from './_common/select-marital-status.vue'
import SelectReligion from './_common/select-religion.vue'
+import Separator from '~/components/pub/ui/separator/Separator.vue'
+
+import * as DE from '~/components/pub/my-ui/doc-entry'
const props = defineProps<{
schema: any
@@ -46,180 +51,161 @@ defineExpose({
validation-mode="onSubmit"
:initial-values="initialValues ? initialValues : {}"
>
-
-
Data Diri Pasien
-
-
-
-
-
-
-
-
-
-
-
-
+
Data Diri Pasien
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
Dokumen Identitas
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Data Demografis
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
Dokumen Identitas
+
+
+
+
diff --git a/app/components/app/patient/list-cfg.ts b/app/components/app/patient/list-cfg.ts
index e8d2b220..559428a7 100644
--- a/app/components/app/patient/list-cfg.ts
+++ b/app/components/app/patient/list-cfg.ts
@@ -1,5 +1,5 @@
import type { Config } from '~/components/pub/my-ui/data-table'
-import type { PatientEntity } from '~/models/patient'
+import type { Patient } from '~/models/patient'
import { defineAsyncComponent } from 'vue'
import { educationCodes, genderCodes } from '~/lib/constants'
import { calculateAge } from '~/lib/utils'
@@ -11,8 +11,9 @@ export const config: Config = {
headers: [
[
+ { label: 'ID' },
{ label: 'Nama' },
- { label: 'NIK' },
+ { label: 'NIK/No. Paspor' },
{ label: 'Tgl Lahir' },
{ label: 'Umur' },
{ label: 'Jenis Kelamin' },
@@ -21,15 +22,7 @@ export const config: Config = {
],
],
- keys: [
- 'name',
- 'identity_number',
- 'birth_date',
- 'patient_age',
- 'gender',
- 'education',
- 'action',
- ],
+ keys: ['patientId', 'name', 'identity_number', 'birth_date', 'patient_age', 'gender', 'education', 'action'],
delKeyNames: [
{ key: 'code', label: 'Kode' },
@@ -37,33 +30,41 @@ export const config: Config = {
],
parses: {
+ patientId: (rec: unknown): unknown => {
+ const patient = rec as Patient
+ return patient.number
+ },
name: (rec: unknown): unknown => {
- const { person } = rec as PatientEntity
+ const { person } = rec as Patient
return person.name.trim()
},
identity_number: (rec: unknown): unknown => {
- const { person } = rec as PatientEntity
- if (person?.residentIdentityNumber?.substring(0, 5) === 'BLANK') {
- return '(TANPA NIK)'
+ const { person } = rec as Patient
+
+ if (person.nationality == 'WNA') {
+ return person.passportNumber
}
- return person.residentIdentityNumber
+
+ return person.residentIdentityNumber || '-'
},
birth_date: (rec: unknown): unknown => {
- const { person } = rec as PatientEntity
- if (typeof person.birthDate === 'object' && person.birthDate) {
- return (person.birthDate as Date).toLocaleDateString()
- } else if (typeof person.birthDate === 'string') {
- return person.birthDate.substring(0, 10)
+ const { person } = rec as Patient
+
+ if (typeof person.birthDate == 'object' && person.birthDate) {
+ return (person.birthDate as Date).toLocaleDateString('id-ID')
+ } else if (typeof person.birthDate == 'string') {
+ return (person.birthDate as string).substring(0, 10)
}
return person.birthDate
},
patient_age: (rec: unknown): unknown => {
- const { person } = rec as PatientEntity
+ const { person } = rec as Patient
return calculateAge(person.birthDate)
},
gender: (rec: unknown): unknown => {
- const { person } = rec as PatientEntity
- if (typeof person.gender_code === 'number' && person.gender_code >= 0) {
+ const { person } = rec as Patient
+
+ if (typeof person.gender_code == 'number' && person.gender_code >= 0) {
return person.gender_code
} else if (typeof person.gender_code === 'string' && person.gender_code) {
return genderCodes[person.gender_code] || '-'
@@ -71,8 +72,8 @@ export const config: Config = {
return '-'
},
education: (rec: unknown): unknown => {
- const { person } = rec as PatientEntity
- if (typeof person.education_code === 'number' && person.education_code >= 0) {
+ const { person } = rec as Patient
+ if (typeof person.education_code == 'number' && person.education_code >= 0) {
return person.education_code
} else if (typeof person.education_code === 'string' && person.education_code) {
return educationCodes[person.education_code] || '-'
diff --git a/app/components/app/patient/list.cfg.ts b/app/components/app/patient/list.cfg.ts
new file mode 100644
index 00000000..d86c8dc1
--- /dev/null
+++ b/app/components/app/patient/list.cfg.ts
@@ -0,0 +1,96 @@
+import type { Config } from '~/components/pub/my-ui/data-table'
+import type { Patient } from '~/models/patient'
+import { defineAsyncComponent } from 'vue'
+import { educationCodes, genderCodes } from '~/lib/constants'
+import { calculateAge } from '~/lib/utils'
+
+const action = defineAsyncComponent(() => import('~/components/pub/my-ui/data/dropdown-action-dud.vue'))
+
+export const config: Config = {
+ cols: [{}, {}, {}, {}, {}, {}, {}, { width: 5 }],
+
+ headers: [
+ [
+ { label: 'No. RM' },
+ { label: 'Nama' },
+ { label: 'No. KTP/SIM/Passpor' },
+ { label: 'Tgl Lahir' },
+ { label: 'Umur' },
+ { label: 'Kelamin' },
+ { label: 'Pendidikan' },
+ { label: '' },
+ ],
+ ],
+
+ keys: ['number', 'person.name', 'identity_number', 'birth_date', 'patient_age', 'gender', 'education', 'action'],
+
+ delKeyNames: [
+ { key: 'code', label: 'Kode' },
+ { key: 'name', label: 'Nama' },
+ ],
+
+ parses: {
+ patientId: (rec: unknown): unknown => {
+ const patient = rec as Patient
+ return patient.number
+ },
+ identity_number: (rec: unknown): unknown => {
+ const { person } = rec as Patient
+
+ if (person.nationality == 'WNA') {
+ return person.passportNumber
+ }
+
+ return person.residentIdentityNumber || '-'
+ },
+ birth_date: (rec: unknown): unknown => {
+ const { person } = rec as Patient
+
+ if (typeof person.birthDate == 'object' && person.birthDate) {
+ return (person.birthDate as Date).toLocaleDateString('id-ID')
+ } else if (typeof person.birthDate == 'string') {
+ return (person.birthDate as string).substring(0, 10)
+ }
+ return person.birthDate
+ },
+ patient_age: (rec: unknown): unknown => {
+ const { person } = rec as Patient
+ return calculateAge(person.birthDate)
+ },
+ gender: (rec: unknown): unknown => {
+ const { person } = rec as Patient
+
+ if (typeof person.gender_code == 'number' && person.gender_code >= 0) {
+ return person.gender_code
+ } else if (typeof person.gender_code === 'string' && person.gender_code) {
+ return genderCodes[person.gender_code] || '-'
+ }
+ return '-'
+ },
+ education: (rec: unknown): unknown => {
+ const { person } = rec as Patient
+ if (typeof person.education_code == 'number' && person.education_code >= 0) {
+ return person.education_code
+ } else if (typeof person.education_code === 'string' && person.education_code) {
+ return educationCodes[person.education_code] || '-'
+ }
+ return '-'
+ },
+ },
+
+ components: {
+ action(rec, idx) {
+ return {
+ idx,
+ rec: rec as object,
+ component: action,
+ }
+ },
+ },
+
+ htmls: {
+ patient_address(_rec) {
+ return '-'
+ },
+ },
+}
diff --git a/app/components/app/patient/list.vue b/app/components/app/patient/list.vue
index c12f409f..8274e752 100644
--- a/app/components/app/patient/list.vue
+++ b/app/components/app/patient/list.vue
@@ -1,7 +1,7 @@
-
-
-
+
-
-
+
+
diff --git a/app/components/app/person-address/_common/select-postal.vue b/app/components/app/person-address/_common/select-postal.vue
index 83b33786..56867c7f 100644
--- a/app/components/app/person-address/_common/select-postal.vue
+++ b/app/components/app/person-address/_common/select-postal.vue
@@ -6,8 +6,11 @@ import Field from '~/components/pub/my-ui/form/field.vue'
import Label from '~/components/pub/my-ui/form/label.vue'
import { cn } from '~/lib/utils'
+import * as DE from '~/components/pub/my-ui/doc-entry'
+
const props = defineProps<{
fieldName: string
+ villageCode?: string
isDisabled?: boolean
placeholder?: string
errors?: FormErrors
@@ -17,37 +20,32 @@ const props = defineProps<{
isRequired?: boolean
}>()
-const {
- fieldName = 'zipCode',
- placeholder = 'Kode Pos',
- errors,
- class: containerClass,
- selectClass,
- fieldGroupClass,
-} = props
+const { fieldName = 'postalRegion', placeholder = 'Kode Pos', errors, class: containerClass, 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 { postalRegionOptions, isLoading, error } = usePostalRegion(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
+})
-
-
-
+
@@ -71,6 +69,6 @@ const postalCodeOptions = [
-
-
+
+
diff --git a/app/components/app/person-address/_common/select-province.vue b/app/components/app/person-address/_common/select-province.vue
index f8cc5464..ca6dd51a 100644
--- a/app/components/app/person-address/_common/select-province.vue
+++ b/app/components/app/person-address/_common/select-province.vue
@@ -6,8 +6,10 @@ import Field from '~/components/pub/my-ui/form/field.vue'
import Label from '~/components/pub/my-ui/form/label.vue'
import { cn } from '~/lib/utils'
+import * as DE from '~/components/pub/my-ui/doc-entry'
+
const props = defineProps<{
- fieldName: string
+ fieldName?: string
isDisabled?: boolean
placeholder?: string
errors?: FormErrors
@@ -18,29 +20,38 @@ 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
+})
-
-
-
+
-
-
+
+
diff --git a/app/components/app/person-address/_common/select-regency.vue b/app/components/app/person-address/_common/select-regency.vue
index 9b415b6e..8fbf6f23 100644
--- a/app/components/app/person-address/_common/select-regency.vue
+++ b/app/components/app/person-address/_common/select-regency.vue
@@ -6,8 +6,11 @@ import Field from '~/components/pub/my-ui/form/field.vue'
import Label from '~/components/pub/my-ui/form/label.vue'
import { cn } from '~/lib/utils'
+import * as DE from '~/components/pub/my-ui/doc-entry'
+
const props = defineProps<{
- fieldName: string
+ fieldName?: string
+ provinceCode?: string
isDisabled?: boolean
placeholder?: string
errors?: FormErrors
@@ -17,26 +20,41 @@ 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({ provinceCode: provinceCodeRef, enablePagination: false })
+
+// 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
+})
-
-
-
+
-
-
+
+
diff --git a/app/components/app/person-address/_common/select-village.vue b/app/components/app/person-address/_common/select-village.vue
index a3d7725c..9684954f 100644
--- a/app/components/app/person-address/_common/select-village.vue
+++ b/app/components/app/person-address/_common/select-village.vue
@@ -6,8 +6,11 @@ import Field from '~/components/pub/my-ui/form/field.vue'
import Label from '~/components/pub/my-ui/form/label.vue'
import { cn } from '~/lib/utils'
+import * as DE from '~/components/pub/my-ui/doc-entry'
+
const props = defineProps<{
- fieldName: string
+ fieldName?: string
+ districtCode?: string
isDisabled?: boolean
placeholder?: string
errors?: FormErrors
@@ -17,25 +20,41 @@ 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
+})
-
-
-
+
-
-
+
+
diff --git a/app/components/app/person-address/entry-form-relative.vue b/app/components/app/person-address/entry-form-relative.vue
index 1c8c91a7..cc294bab 100644
--- a/app/components/app/person-address/entry-form-relative.vue
+++ b/app/components/app/person-address/entry-form-relative.vue
@@ -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'
@@ -14,6 +15,8 @@ import SelectRegency from './_common/select-regency.vue'
import SelectVillage from './_common/select-village.vue'
import { Form } from '~/components/pub/ui/form'
+import * as DE from '~/components/pub/my-ui/doc-entry'
+
const props = defineProps<{
title: string
conf?: {
@@ -39,18 +42,27 @@ let isResetting = false
// Field dependency map for placeholder
const fieldStates: Record
= {
- regencyId: { dependsOn: 'provinceId', 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' },
+ postalRegion_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 +75,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', 'postalRegion_code'].includes(field)) {
return { placeholder: '-', disabled: true }
}
@@ -73,7 +85,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', 'postalRegion_code'].includes(field)) {
return { placeholder: '-', disabled: true }
}
@@ -84,9 +96,9 @@ function getFieldState(field: string) {
// #region watch
-// Watch provinceId changes
+// Watch province_code changes
watch(
- () => formRef.value?.values?.provinceId,
+ () => formRef.value?.values?.province_code,
(newValue, oldValue) => {
if (isResetting || !formRef.value || newValue === oldValue) return
@@ -95,10 +107,10 @@ watch(
formRef.value.setValues(
{
- regencyId: undefined,
- districtId: undefined,
- villageId: undefined,
- zipCode: undefined,
+ regency_code: undefined,
+ district_code: undefined,
+ village_code: undefined,
+ postalRegion_code: undefined,
},
false,
)
@@ -110,9 +122,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 +133,9 @@ watch(
formRef.value.setValues(
{
- districtId: undefined,
- villageId: undefined,
- zipCode: undefined,
+ district_code: undefined,
+ village_code: undefined,
+ postalRegion_code: undefined,
},
false,
)
@@ -135,9 +147,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 +158,8 @@ watch(
formRef.value.setValues(
{
- villageId: undefined,
- zipCode: undefined,
+ village_code: undefined,
+ postalRegion_code: undefined,
},
false,
)
@@ -159,9 +171,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 +182,7 @@ watch(
formRef.value.setValues(
{
- zipCode: undefined,
+ postalRegion_code: undefined,
},
false,
)
@@ -188,19 +200,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.provinceId === '') updatedValues.provinceId = 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.postalRegion_code === '') updatedValues.postalRegion_code = undefined
if (updatedValues.address === '') updatedValues.address = undefined
// Update values dan trigger validasi
@@ -213,15 +229,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('provinceId', 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('postalRegion_code', undefined)
formRef.value?.setFieldError('address', undefined)
formRef.value?.setFieldError('rt', undefined)
formRef.value?.setFieldError('rw', undefined)
@@ -241,129 +257,138 @@ watch(
:validation-schema="formSchema"
:validate-on-mount="false"
validation-mode="onSubmit"
- :initial-values="initialValues ?? { isSameAddress: '1' }"
+ :initial-values="initialValues ?? { isSameAddress: '1', locationType_code: 'identity' }"
>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+ Apakah alamat KTP sama dengan alamat sekarang?
+
+
+
+
+
+ componentField.onChange(value)"
+ class="flex flex-row flex-wrap gap-4 sm:gap-6"
+ >
+
+
+
+ {{ option.label }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
diff --git a/app/components/app/person-address/entry-form.vue b/app/components/app/person-address/entry-form.vue
index a26381d8..b3ef300d 100644
--- a/app/components/app/person-address/entry-form.vue
+++ b/app/components/app/person-address/entry-form.vue
@@ -12,6 +12,8 @@ import SelectRegency from './_common/select-regency.vue'
import SelectVillage from './_common/select-village.vue'
import { Form } from '~/components/pub/ui/form'
+import * as DE from '~/components/pub/my-ui/doc-entry'
+
const props = defineProps<{
title: string
conf?: {
@@ -35,29 +37,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 +78,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 +108,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)
}
},
)
@@ -145,124 +162,84 @@ watch(
:validation-schema="formSchema"
:validate-on-mount="false"
validation-mode="onSubmit"
- :initial-values="initialValues ? initialValues : {}"
+ :initial-values="
+ initialValues ? { locationType_code: 'domicile', ...initialValues } : { locationType_code: 'domicile' }
+ "
>
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
+
diff --git a/app/components/app/person-contact/entry-form.vue b/app/components/app/person-contact/entry-form.vue
index 5fa451b4..431a3c79 100644
--- a/app/components/app/person-contact/entry-form.vue
+++ b/app/components/app/person-contact/entry-form.vue
@@ -39,7 +39,7 @@ defineExpose({
{{ props.title || 'Kontak Pasien' }}
@@ -94,6 +94,7 @@ defineExpose({
:disabled="fields.length >= contactLimit"
type="button"
variant="outline"
+ size="sm"
@click="push({ contactType: '', contactNumber: '' })"
>
({
+ label,
+ value,
+ ...(value === 'other' && { priority: -1 })
+}))
diff --git a/app/components/app/person/_common/select-birth-place.vue b/app/components/app/person/_common/select-birth-place.vue
new file mode 100644
index 00000000..dd61a69f
--- /dev/null
+++ b/app/components/app/person/_common/select-birth-place.vue
@@ -0,0 +1,77 @@
+
+
+
+
+
+ Tempat Lahir
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/components/app/person/family-parents-form.vue b/app/components/app/person/family-parents-form.vue
index e5ac4a46..fc87ce45 100644
--- a/app/components/app/person/family-parents-form.vue
+++ b/app/components/app/person/family-parents-form.vue
@@ -65,7 +65,7 @@ defineExpose({
{{ props.title }}
diff --git a/app/components/content/patient/detail.vue b/app/components/content/patient/detail.vue
index 185b3707..da9b2809 100644
--- a/app/components/content/patient/detail.vue
+++ b/app/components/content/patient/detail.vue
@@ -1,7 +1,7 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ emptyMessage || 'Item tidak ditemukan.' }}
+
+
+
+
+
+
+
{{ item.label }}
+
+
+ {{ item.code }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ props.page || 1 }} / {{ props.totalPage || 1 }}
+
+
+
+
+
+
+
+
+
+
diff --git a/app/components/pub/my-ui/combobox/combobox.vue b/app/components/pub/my-ui/combobox/combobox.vue
index 4c0c5bb5..23505393 100644
--- a/app/components/pub/my-ui/combobox/combobox.vue
+++ b/app/components/pub/my-ui/combobox/combobox.vue
@@ -23,7 +23,7 @@ const open = ref(false)
const selectedItem = computed(() => props.items.find((item) => item.value === props.modelValue))
const displayText = computed(() => {
- console.log(selectedItem);
+ console.log(selectedItem)
if (selectedItem.value?.label) {
return selectedItem.value.label
}
@@ -31,7 +31,7 @@ const displayText = computed(() => {
})
watch(props, () => {
- console.log(props.modelValue);
+ console.log(props.modelValue)
})
const searchableItems = computed(() => {
@@ -74,10 +74,11 @@ function onSelect(item: Item) {
:aria-describedby="`${props.id}-search`"
:class="
cn(
- 'w-full justify-between border dark:!border-slate-400 h-8 2xl:h-9 md:text-xs 2xl:text-sm font-normal rounded-md px-3 focus:outline-none focus:ring-1 focus:ring-black dark:focus:ring-white',
+ 'h-8 w-full justify-between rounded-md border px-3 font-normal focus:outline-none focus:ring-1 focus:ring-black dark:!border-slate-400 dark:focus:ring-white md:text-xs 2xl:h-9 2xl:text-sm',
{
- 'cursor-not-allowed bg-gray-100 opacity-50 border-gray-300 text-gray-500': props.isDisabled,
- 'bg-white text-black dark:bg-slate-950 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-slate-950 dark:text-white dark:hover:bg-gray-700':
+ !props.isDisabled,
'text-gray-400 dark:text-gray-500': !modelValue && !props.isDisabled,
},
props.class,
@@ -87,22 +88,26 @@ function onSelect(item: Item) {
{{ displayText }}
-
+
-
+
{{ emptyMessage || 'Item tidak ditemukan.' }}
-
+
val === 'YA')
+const IsNewBornSchema = z
+ .enum(['YA', 'TIDAK'], {
+ required_error: 'Mohon lengkapi status pasien',
+ })
+ .transform((val) => val === 'YA')
+
+const ACCEPTED_UPLOAD_TYPES = ['image/jpeg', 'image/png', 'application/pdf']
+
const PatientSchema = z
.object({
// Data Diri Pasien
- identityNumber: z
- .string({
- required_error: 'Mohon lengkapi NIK',
+ identityNumber: z.string().optional(),
+ // .string({
+ // required_error: 'Mohon lengkapi NIK',
+ // })
+ // .min(16, 'NIK harus berupa angka 16 digit')
+ // .regex(/^\d+$/, 'NIK harus berupa angka 16 digit'),
+ residentIdentityFile: z
+ .any()
+ .optional()
+ .refine((f) => !f || f instanceof File, { message: 'Harus berupa file yang valid' })
+ .refine((f) => !f || ACCEPTED_UPLOAD_TYPES.includes(f.type), {
+ message: 'Format file harus JPG, PNG, atau PDF',
})
- .min(16, 'NIK harus berupa angka 16 digit')
- .regex(/^\d+$/, 'NIK harus berupa angka 16 digit'),
- // identityCardFile: z.instanceof(File, { message: 'File KTP harus dipilih' }),
- // familyCardFile: z.instanceof(File, { message: 'File KK harus dipilih' }),
+ .refine((f) => !f || f.size <= 1 * 1024 * 1024, { message: 'Maksimal 1MB' }),
+
+ familyIdentityFile: z
+ .any()
+ .optional()
+ .refine((f) => !f || f instanceof File, { message: 'Harus berupa file yang valid' })
+ .refine((f) => !f || ACCEPTED_UPLOAD_TYPES.includes(f.type), {
+ message: 'Format file harus JPG, PNG, atau PDF',
+ })
+ .refine((f) => !f || f.size <= 1 * 1024 * 1024, { message: 'Maksimal 1MB' }),
+ // .refine(f => ['image/jpeg', 'image/png'].includes(f.type), 'Hanya JPG/PNG')
// 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 +95,7 @@ const PatientSchema = z
nationality: z.string({
required_error: 'Pilih Kebangsaan',
}),
+ isNewBorn: IsNewBornSchema,
language: z.string({
required_error: 'Mohon pilih Preferensi Bahasa',
}),
@@ -99,13 +124,52 @@ 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'",
- path: ['disabilityType'],
- })
- .refine((data) => (data.disability === 'YA' ? !!data.disabilityType?.trim() : true), {
- message: 'Mohon pilih Jenis Disabilitas',
- path: ['disabilityType'],
+ .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) => {
+ // Jika disability = 'YA', maka disabilityType wajib diisi
+ if (data.disability === 'YA') {
+ return !!data.disabilityType?.trim()
+ }
+ return true
+ },
+ {
+ message: 'Mohon pilih Jenis Disabilitas',
+ path: ['disabilityType'],
+ },
+ )
+ // .refine((data) => {
+ // // Jika nationality = 'WNA', maka passportNumber wajib diisi
+ // if (data.nationality === 'WNA') {
+ // return !!data.passportNumber?.trim()
+ // }
+ // return true
+ // }, {
+ // message: 'Nomor Paspor wajib diisi untuk Warga Negara Asing',
+ // path: ['passportNumber'],
+ // })
+ .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
diff --git a/app/schemas/person-address-relative.schema.ts b/app/schemas/person-address-relative.schema.ts
index 43bac06e..091da1df 100644
--- a/app/schemas/person-address-relative.schema.ts
+++ b/app/schemas/person-address-relative.schema.ts
@@ -1,25 +1,58 @@
import { z } from 'zod'
import { PersonAddressSchema } from './person-address.schema'
-// Schema untuk alamat opsional ketika isSameAddress = '1'
-const OptionalAddressSchema = PersonAddressSchema.partial()
+const PersonAddressRelativeSchema = z
+ .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
-// Schema untuk alamat required ketika isSameAddress = '0'
-const RequiredAddressSchema = PersonAddressSchema
+ // Jika alamat tidak sama ('0'), maka semua field address wajib diisi
+ if (isSameAddress === '0') {
+ const requiredFields = [
+ 'province_code',
+ 'regency_code',
+ 'district_code',
+ 'village_code',
+ 'postalRegion_code',
+ 'address',
+ ]
-const PersonAddressRelativeSchema = z.discriminatedUnion('isSameAddress', [
- z
- .object({
- isSameAddress: z.literal('1').default('1'),
- })
- .merge(OptionalAddressSchema),
+ requiredFields.forEach((field) => {
+ if (!data[field as keyof typeof data]) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: getRequiredMessage(field),
+ path: [field],
+ })
+ }
+ })
+ }
+ })
+ .transform((data) => ({
+ ...data,
+ isSameAddress: data.isSameAddress === '1',
+ }))
- z
- .object({
- isSameAddress: z.literal('0'),
- })
- .merge(RequiredAddressSchema),
-])
+function getRequiredMessage(field: string): string {
+ const messages: Record = {
+ province_code: 'Mohon pilih provinsi',
+ regency_code: 'Mohon pilih kabupaten/kota',
+ district_code: 'Mohon pilih kecamatan',
+ village_code: 'Mohon pilih kelurahan',
+ postalRegion_code: 'Mohon lengkapi kode pos',
+ address: 'Mohon lengkapi alamat',
+ }
+ return messages[field] || `${field} wajib diisi`
+}
type PersonAddressRelativeFormData = z.infer
diff --git a/app/schemas/person-address.schema.ts b/app/schemas/person-address.schema.ts
index 93ce6396..3e991bad 100644
--- a/app/schemas/person-address.schema.ts
+++ b/app/schemas/person-address.schema.ts
@@ -1,6 +1,9 @@
import { z } from 'zod'
const PersonAddressSchema = z.object({
+ locationType_code: z.string({
+ required_error: 'Mohon pilih jenis alamat',
+ }),
address: z.string({
required_error: 'Mohon lengkapi alamat',
}),
@@ -16,8 +19,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({
+ postalRegion_code: z.string({
required_error: 'Mohon lengkapi kode pos',
}),
// .min(5, 'Kode pos harus berupa angka 5 digit')
diff --git a/app/services/district.service.ts b/app/services/district.service.ts
new file mode 100644
index 00000000..fd817b3a
--- /dev/null
+++ b/app/services/district.service.ts
@@ -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)
+}
diff --git a/app/services/patient.service.ts b/app/services/patient.service.ts
index b28f92ab..7591f356 100644
--- a/app/services/patient.service.ts
+++ b/app/services/patient.service.ts
@@ -1,4 +1,5 @@
import { xfetch } from '~/composables/useXfetch'
+import { uploadCode, type UploadCodeKey } from '~/lib/constants'
const mainUrl = '/api/v1/patient'
@@ -77,3 +78,30 @@ export async function removePatient(id: number) {
throw new Error('Failed to delete patient')
}
}
+
+export async function uploadAttachment(file: File, userId: number, key: UploadCodeKey) {
+ try {
+ const resolvedKey = uploadCode[key]
+ if (!resolvedKey) {
+ throw new Error(`Invalid upload code key: ${key}`)
+ }
+
+ // siapkan form-data body
+ const formData = new FormData()
+ formData.append('code', resolvedKey)
+ formData.append('content', file)
+
+ // kirim via xfetch
+ const resp = await xfetch(`${mainUrl}/${userId}/upload`, 'POST', formData)
+
+ // struktur hasil sama seperti patchPatient
+ const result: any = {}
+ result.success = resp.success
+ result.body = (resp.body as Record) || {}
+
+ return result
+ } catch (error) {
+ console.error('Error uploading attachment:', error)
+ throw new Error('Failed to upload attachment')
+ }
+}
diff --git a/app/services/postal-region.service.ts b/app/services/postal-region.service.ts
new file mode 100644
index 00000000..8273dca0
--- /dev/null
+++ b/app/services/postal-region.service.ts
@@ -0,0 +1,17 @@
+// Base service for postal code operations
+import * as base from './_crud-base'
+
+const path = '/api/v1/postal-region'
+const name = 'postal-region'
+
+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)
+}
diff --git a/app/services/province.service.ts b/app/services/province.service.ts
new file mode 100644
index 00000000..0fb26e7a
--- /dev/null
+++ b/app/services/province.service.ts
@@ -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)
+}
diff --git a/app/services/regency.service.ts b/app/services/regency.service.ts
new file mode 100644
index 00000000..c5919610
--- /dev/null
+++ b/app/services/regency.service.ts
@@ -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)
+}
diff --git a/app/services/village.service.ts b/app/services/village.service.ts
new file mode 100644
index 00000000..b41bd9d5
--- /dev/null
+++ b/app/services/village.service.ts
@@ -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)
+}