refactor(patient): rename entry component to add and clean up form error props
Remove FormErrors type imports and error props from all form components Move content from entry.vue to add.vue with updated title styling
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
<script setup lang="ts">
|
||||
import type { FormErrors } from '~/types/error'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
import { Form } from '~/components/pub/ui/form'
|
||||
|
||||
@@ -28,7 +27,6 @@ import {
|
||||
const props = defineProps<{
|
||||
schema: any
|
||||
initialValues?: any
|
||||
errors?: FormErrors
|
||||
}>()
|
||||
|
||||
const formSchema = toTypedSchema(props.schema)
|
||||
@@ -62,7 +60,6 @@ defineExpose({
|
||||
field-name="identityNumber"
|
||||
label="No. KTP"
|
||||
placeholder="Masukkan NIK"
|
||||
:errors="errors"
|
||||
numeric-only
|
||||
/>
|
||||
<InputBase
|
||||
@@ -71,68 +68,58 @@ defineExpose({
|
||||
placeholder="Masukkan nomor SIM"
|
||||
numeric-only
|
||||
:max-length="20"
|
||||
:errors="errors"
|
||||
/>
|
||||
<InputBase
|
||||
field-name="passportNumber"
|
||||
label="No. Paspor"
|
||||
placeholder="Masukkan nomor paspor"
|
||||
:max-length="20"
|
||||
:errors="errors"
|
||||
/>
|
||||
<InputName
|
||||
field-name-alias="alias"
|
||||
field-name-input="fullName"
|
||||
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
|
||||
/>
|
||||
<SelectGender
|
||||
field-name="gender"
|
||||
label="Jenis Kelamin"
|
||||
placeholder="Pilih jenis kelamin"
|
||||
:errors="errors"
|
||||
is-required
|
||||
/>
|
||||
<SelectBirthPlace
|
||||
field-name="birthPlace"
|
||||
label="Tempat Lahir"
|
||||
placeholder="Pilih tempat lahir"
|
||||
:errors="errors"
|
||||
is-required
|
||||
/>
|
||||
<SelectDob
|
||||
label="Tanggal Lahir"
|
||||
:errors="errors"
|
||||
is-required
|
||||
/>
|
||||
<SelectEducation
|
||||
field-name="education"
|
||||
label="Pendidikan"
|
||||
placeholder="Pilih pendidikan"
|
||||
:errors="errors"
|
||||
is-required
|
||||
/>
|
||||
<SelectJob
|
||||
field-name="job"
|
||||
label="Pekerjaan"
|
||||
placeholder="Pilih pekerjaan"
|
||||
:errors="errors"
|
||||
is-required
|
||||
/>
|
||||
<SelectMaritalStatus
|
||||
field-name="maritalStatus"
|
||||
label="Status Perkawinan"
|
||||
placeholder="Pilih status Perkawinan"
|
||||
:errors="errors"
|
||||
is-required
|
||||
/>
|
||||
<DE.Cell />
|
||||
@@ -140,46 +127,39 @@ defineExpose({
|
||||
field-name="nationality"
|
||||
label="Kebangsaan"
|
||||
placeholder="Pilih kebangsaan"
|
||||
:errors="errors"
|
||||
is-required
|
||||
/>
|
||||
<SelectEthnicity
|
||||
field-name="ethnicity"
|
||||
label="Suku"
|
||||
placeholder="Pilih suku bangsa"
|
||||
:errors="errors"
|
||||
:is-disabled="values.nationality !== 'WNI'"
|
||||
/>
|
||||
<SelectLanguage
|
||||
field-name="language"
|
||||
label="Bahasa"
|
||||
placeholder="Pilih preferensi bahasa"
|
||||
:errors="errors"
|
||||
is-required
|
||||
/>
|
||||
<SelectReligion
|
||||
field-name="religion"
|
||||
label="Agama"
|
||||
placeholder="Pilih agama"
|
||||
:errors="errors"
|
||||
is-required
|
||||
/>
|
||||
<RadioCommunicationBarrier
|
||||
field-name="communicationBarrier"
|
||||
label="Hambatan Berkomunikasi"
|
||||
:errors="errors"
|
||||
is-required
|
||||
/>
|
||||
<RadioDisability
|
||||
field-name="disability"
|
||||
label="Disabilitas"
|
||||
:errors="errors"
|
||||
is-required
|
||||
/>
|
||||
<SelectDisability
|
||||
label="Jenis Disabilitas"
|
||||
field-name="disabilityType"
|
||||
:errors="errors"
|
||||
:is-disabled="values.disability !== 'YA'"
|
||||
:is-required="values.disability === 'YA'"
|
||||
/>
|
||||
@@ -187,7 +167,6 @@ defineExpose({
|
||||
field-name="note"
|
||||
label="Kepercayaan"
|
||||
placeholder="Contoh: tidak ingin diperiksa oleh dokter laki-laki"
|
||||
:errors="errors"
|
||||
/>
|
||||
</DE.Block>
|
||||
|
||||
@@ -202,7 +181,6 @@ defineExpose({
|
||||
field-name="identityCardFile"
|
||||
label="Dokumen KTP"
|
||||
placeholder="Unggah scan dokumen KTP"
|
||||
:errors="errors"
|
||||
:accept="['pdf', 'jpg', 'png']"
|
||||
:max-size-mb="1"
|
||||
/>
|
||||
@@ -210,7 +188,6 @@ defineExpose({
|
||||
field-name="familyCardFile"
|
||||
label="Dokumen KK"
|
||||
placeholder="Unggah scan dokumen KK"
|
||||
:errors="errors"
|
||||
:accept="['pdf', 'jpg', 'png']"
|
||||
:max-size-mb="1"
|
||||
/>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<script setup lang="ts">
|
||||
import type { FormErrors } from '~/types/error'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
|
||||
// components
|
||||
@@ -17,7 +16,6 @@ const props = defineProps<{
|
||||
}
|
||||
schema: any
|
||||
initialValues?: any
|
||||
errors?: FormErrors
|
||||
}>()
|
||||
|
||||
const formSchema = toTypedSchema(props.schema)
|
||||
@@ -283,10 +281,7 @@ watch(
|
||||
>
|
||||
Apakah alamat KTP sama dengan alamat sekarang?
|
||||
</DE.Label>
|
||||
<DE.Field
|
||||
id="isSameAddress"
|
||||
:errors="errors"
|
||||
>
|
||||
<DE.Field id="isSameAddress">
|
||||
<FormField
|
||||
v-slot="{ componentField }"
|
||||
name="isSameAddress"
|
||||
@@ -354,7 +349,6 @@ watch(
|
||||
label="Alamat"
|
||||
:placeholder="getFieldState('address').placeholder"
|
||||
:is-disabled="getFieldState('address').disabled"
|
||||
:errors="errors"
|
||||
:is-required="values.isSameAddress !== true && values.isSameAddress !== '1'"
|
||||
:col-span="2"
|
||||
/>
|
||||
@@ -362,7 +356,6 @@ watch(
|
||||
<InputBase
|
||||
field-name="rt"
|
||||
label="RT"
|
||||
:errors="errors"
|
||||
numeric-only
|
||||
:max-length="2"
|
||||
:placeholder="getFieldState('rt').placeholder"
|
||||
@@ -373,7 +366,6 @@ watch(
|
||||
label="RW"
|
||||
:placeholder="getFieldState('rw').placeholder"
|
||||
:is-disabled="getFieldState('rw').disabled"
|
||||
:errors="errors"
|
||||
:max-length="2"
|
||||
numeric-only
|
||||
/>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<script setup lang="ts">
|
||||
import type { FormErrors } from '~/types/error'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
|
||||
// components
|
||||
@@ -15,7 +14,6 @@ const props = defineProps<{
|
||||
}
|
||||
schema: any
|
||||
initialValues?: any
|
||||
errors?: FormErrors
|
||||
}>()
|
||||
|
||||
const formSchema = toTypedSchema(props.schema)
|
||||
@@ -207,7 +205,6 @@ watch(
|
||||
field-name="address"
|
||||
label="Alamat"
|
||||
placeholder="Masukkan alamat"
|
||||
:errors="errors"
|
||||
is-required
|
||||
:col-span="2"
|
||||
/>
|
||||
@@ -217,7 +214,6 @@ watch(
|
||||
field-name="rt"
|
||||
label="RT"
|
||||
placeholder="01"
|
||||
:errors="errors"
|
||||
numeric-only
|
||||
:max-length="2"
|
||||
/>
|
||||
@@ -225,7 +221,6 @@ watch(
|
||||
field-name="rw"
|
||||
label="RW"
|
||||
placeholder="02"
|
||||
:errors="errors"
|
||||
:max-length="2"
|
||||
numeric-only
|
||||
/>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<script setup lang="ts">
|
||||
import type { FormErrors } from '~/types/error'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
|
||||
// components
|
||||
@@ -13,7 +12,6 @@ const props = defineProps<{
|
||||
schema: any
|
||||
contactLimit: number
|
||||
initialValues?: any
|
||||
errors?: FormErrors
|
||||
}>()
|
||||
|
||||
const { contactLimit = 5 } = props
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<script setup lang="ts">
|
||||
import type { FormErrors } from '~/types/error'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
import { FieldArray } from 'vee-validate'
|
||||
import InputBase from '~/components/pub/my-ui/form/input-base.vue'
|
||||
@@ -11,7 +10,6 @@ const props = defineProps<{
|
||||
title: string
|
||||
schema: any
|
||||
initialValues?: any
|
||||
errors?: FormErrors
|
||||
}>()
|
||||
|
||||
const formSchema = toTypedSchema(props.schema)
|
||||
@@ -82,7 +80,6 @@ defineExpose({
|
||||
<SelectContactRelation
|
||||
:field-name="`contacts[${idx}].relation`"
|
||||
placeholder="Pilih"
|
||||
:errors="errors"
|
||||
field-group-class="mb-0"
|
||||
/>
|
||||
</div>
|
||||
@@ -98,7 +95,6 @@ defineExpose({
|
||||
label=""
|
||||
:field-name="`contacts[${idx}].name`"
|
||||
placeholder="Masukkan Nama"
|
||||
:errors="errors"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -113,7 +109,6 @@ defineExpose({
|
||||
:field-name="`contacts[${idx}].address`"
|
||||
label=""
|
||||
placeholder="Masukkan Alamat"
|
||||
:errors="errors"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -130,7 +125,6 @@ defineExpose({
|
||||
placeholder="081234567890"
|
||||
:max-length="15"
|
||||
numeric-only
|
||||
:errors="errors"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import type { PersonFamilyFormData as FamilyData } from '~/schemas/person-family.schema'
|
||||
import type { FormErrors } from '~/types/error'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
import { FieldArray } from 'vee-validate'
|
||||
|
||||
@@ -14,7 +13,6 @@ const props = defineProps<{
|
||||
title: string
|
||||
schema: any
|
||||
initialValues?: any
|
||||
errors?: FormErrors
|
||||
}>()
|
||||
|
||||
const formSchema = toTypedSchema(props.schema)
|
||||
@@ -118,7 +116,6 @@ defineExpose({
|
||||
? 'Masukkan nama ayah pasien'
|
||||
: 'Masukkan nama'
|
||||
"
|
||||
:errors="errors"
|
||||
is-required
|
||||
/>
|
||||
</div>
|
||||
@@ -134,7 +131,6 @@ defineExpose({
|
||||
: 'Pendidikan'
|
||||
"
|
||||
placeholder="Pilih"
|
||||
:errors="errors"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -156,7 +152,6 @@ defineExpose({
|
||||
? 'Masukkan pekerjaan ayah'
|
||||
: 'Masukkan pekerjaan'
|
||||
"
|
||||
:errors="errors"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,322 @@
|
||||
<script setup lang="ts"></script>
|
||||
<script setup lang="ts">
|
||||
// type
|
||||
import type { Patient, genPatientProps } from '~/models/patient'
|
||||
import type { ExposedForm } from '~/types/form'
|
||||
import type { PatientBase } from '~/models/patient'
|
||||
|
||||
// schema and models
|
||||
import { genPatient } from '~/models/patient'
|
||||
import { PatientSchema } from '~/schemas/patient.schema'
|
||||
import { PersonAddressRelativeSchema } from '~/schemas/person-address-relative.schema'
|
||||
import { PersonAddressSchema } from '~/schemas/person-address.schema'
|
||||
import { PersonContactListSchema } from '~/schemas/person-contact.schema'
|
||||
import { PersonFamiliesSchema } from '~/schemas/person-family.schema'
|
||||
import { ResponsiblePersonSchema } from '~/schemas/person-relative.schema'
|
||||
|
||||
// components
|
||||
import Action from '~/components/pub/my-ui/nav-footer/ba-dr-su.vue'
|
||||
import AppPatientEntryForm from '~/components/app/patient/entry-form.vue'
|
||||
import AppPersonAddressEntryForm from '~/components/app/person-address/entry-form.vue'
|
||||
import AppPersonAddressEntryFormRelative from '~/components/app/person-address/entry-form-relative.vue'
|
||||
import AppPersonFamilyParentsForm from '~/components/app/person/family-parents-form.vue'
|
||||
import AppPersonContactEntryForm from '~/components/app/person-contact/entry-form.vue'
|
||||
import AppPersonRelativeEntryForm from '~/components/app/person-relative/entry-form.vue'
|
||||
|
||||
// services
|
||||
import { uploadAttachment } from '~/services/patient.service'
|
||||
|
||||
import {
|
||||
// for form entry
|
||||
isReadonly,
|
||||
isProcessing,
|
||||
isFormEntryDialogOpen,
|
||||
isRecordConfirmationOpen,
|
||||
onResetState,
|
||||
handleActionSave,
|
||||
handleCancelForm,
|
||||
} from '~/handlers/patient.handler'
|
||||
|
||||
import { toast } from '~/components/pub/ui/toast'
|
||||
|
||||
// #region Props & Emits
|
||||
const props = defineProps<{
|
||||
callbackUrl?: string
|
||||
}>()
|
||||
|
||||
const residentIdentityFile = ref<File>()
|
||||
const familyCardFile = ref<File>()
|
||||
|
||||
// form related state
|
||||
const personAddressForm = ref<ExposedForm<any> | null>(null)
|
||||
const personAddressRelativeForm = ref<ExposedForm<any> | null>(null)
|
||||
const personContactForm = ref<ExposedForm<any> | null>(null)
|
||||
const personEmergencyContactRelative = ref<ExposedForm<any> | null>(null)
|
||||
const personFamilyForm = ref<ExposedForm<any> | null>(null)
|
||||
const personPatientForm = ref<ExposedForm<any> | null>(null)
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region State & Computed
|
||||
// #endregion
|
||||
|
||||
// #region Lifecycle Hooks
|
||||
onMounted(() => {
|
||||
// Initial synchronization when forms are mounted and isSameAddress is true by default
|
||||
nextTick(() => {
|
||||
const isSameAddress = personAddressRelativeForm.value?.values?.isSameAddress
|
||||
if (
|
||||
(isSameAddress === true || isSameAddress === '1') &&
|
||||
personAddressForm.value?.values &&
|
||||
personAddressRelativeForm.value
|
||||
) {
|
||||
const currentAddressValues = personAddressForm.value.values
|
||||
if (Object.keys(currentAddressValues).length > 0) {
|
||||
personAddressRelativeForm.value.setValues(
|
||||
{
|
||||
...personAddressRelativeForm.value.values,
|
||||
province_code: currentAddressValues.province_code || undefined,
|
||||
regency_code: currentAddressValues.regency_code || undefined,
|
||||
district_code: currentAddressValues.district_code || undefined,
|
||||
village_code: currentAddressValues.village_code || undefined,
|
||||
postalRegion_code: currentAddressValues.postalRegion_code || undefined,
|
||||
address: currentAddressValues.address || undefined,
|
||||
rt: currentAddressValues.rt || undefined,
|
||||
rw: currentAddressValues.rw || undefined,
|
||||
},
|
||||
false,
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
// #endregion
|
||||
|
||||
// #region Functions
|
||||
async function composeFormData(): Promise<Patient> {
|
||||
const [patient, address, addressRelative, families, contacts, emergencyContact] = await Promise.all([
|
||||
personPatientForm.value?.validate(),
|
||||
personAddressForm.value?.validate(),
|
||||
personAddressRelativeForm.value?.validate(),
|
||||
personFamilyForm.value?.validate(),
|
||||
personContactForm.value?.validate(),
|
||||
personEmergencyContactRelative.value?.validate(),
|
||||
])
|
||||
|
||||
const results = [patient, address, addressRelative, families, contacts, emergencyContact]
|
||||
console.log(results)
|
||||
const allValid = results.every((r) => r?.valid)
|
||||
|
||||
// exit, if form errors happend during validation
|
||||
// for example: dropdown not selected
|
||||
if (!allValid) return Promise.reject('Form validation failed')
|
||||
|
||||
const formDataRequest: genPatientProps = {
|
||||
patient: patient?.values,
|
||||
residentAddress: address?.values,
|
||||
cardAddress: addressRelative?.values,
|
||||
familyData: families?.values,
|
||||
contacts: contacts?.values,
|
||||
responsible: emergencyContact?.values,
|
||||
}
|
||||
|
||||
const formData = genPatient()
|
||||
|
||||
if (patient?.values.residentIdentityFile) {
|
||||
residentIdentityFile.value = patient?.values.residentIdentityFile
|
||||
}
|
||||
|
||||
if (patient?.values.familyIdentityFile) {
|
||||
familyCardFile.value = patient?.values.familyIdentityFile
|
||||
}
|
||||
|
||||
return new Promise((resolve) => resolve(formData))
|
||||
}
|
||||
// #endregion region
|
||||
|
||||
// #region Utilities & event handlers
|
||||
async function handleActionClick(eventType: string) {
|
||||
if (eventType === 'submit') {
|
||||
const patient: Patient = await composeFormData()
|
||||
let createdPatientId = 0
|
||||
|
||||
const response = await handleActionSave(
|
||||
patient,
|
||||
() => {},
|
||||
() => {},
|
||||
toast,
|
||||
)
|
||||
|
||||
const data = (response?.body?.data ?? null) as PatientBase | null
|
||||
if (!data) return
|
||||
createdPatientId = data.id
|
||||
|
||||
if (residentIdentityFile.value) {
|
||||
void uploadAttachment(residentIdentityFile.value, createdPatientId, 'ktp')
|
||||
}
|
||||
if (familyCardFile.value) {
|
||||
void uploadAttachment(familyCardFile.value, createdPatientId, 'kk')
|
||||
}
|
||||
|
||||
// If has callback provided redirect to callback with patientData
|
||||
if (props.callbackUrl) {
|
||||
await navigateTo(props.callbackUrl + '?patient-id=' + patient.id)
|
||||
return
|
||||
}
|
||||
|
||||
// Navigate to patient list or show success message
|
||||
await navigateTo('/client/patient')
|
||||
return
|
||||
}
|
||||
|
||||
if (eventType === 'cancel') {
|
||||
if (props.callbackUrl) {
|
||||
await navigateTo(props.callbackUrl)
|
||||
return
|
||||
}
|
||||
|
||||
await navigateTo({
|
||||
name: 'client-patient',
|
||||
})
|
||||
// handleCancelForm()
|
||||
}
|
||||
}
|
||||
// #endregion
|
||||
|
||||
// #region Watchers
|
||||
// Watcher untuk sinkronisasi initial ketika kedua form sudah ready
|
||||
watch(
|
||||
[() => personAddressForm.value, () => personAddressRelativeForm.value],
|
||||
([addressForm, relativeForm]) => {
|
||||
if (addressForm && relativeForm) {
|
||||
// Trigger initial sync jika isSameAddress adalah true
|
||||
nextTick(() => {
|
||||
const isSameAddress = relativeForm.values?.isSameAddress
|
||||
if ((isSameAddress === true || isSameAddress === '1') && addressForm.values) {
|
||||
const currentAddressValues = addressForm.values
|
||||
if (Object.keys(currentAddressValues).length > 0) {
|
||||
relativeForm.setValues(
|
||||
{
|
||||
...relativeForm.values,
|
||||
province_code: currentAddressValues.province_code || undefined,
|
||||
regency_code: currentAddressValues.regency_code || undefined,
|
||||
district_code: currentAddressValues.district_code || undefined,
|
||||
village_code: currentAddressValues.village_code || undefined,
|
||||
postalRegion_code: currentAddressValues.postalRegion_code || undefined,
|
||||
address: currentAddressValues.address || undefined,
|
||||
rt: currentAddressValues.rt || undefined,
|
||||
rw: currentAddressValues.rw || undefined,
|
||||
},
|
||||
false,
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
// Watcher untuk sinkronisasi alamat ketika isSameAddress = true
|
||||
watch(
|
||||
() => personAddressForm.value?.values,
|
||||
(newAddressValues) => {
|
||||
// Cek apakah alamat KTP harus sama dengan alamat sekarang
|
||||
const isSameAddress = personAddressRelativeForm.value?.values?.isSameAddress
|
||||
|
||||
if ((isSameAddress === true || isSameAddress === '1') && newAddressValues && personAddressRelativeForm.value) {
|
||||
// Sinkronkan semua field alamat dari alamat sekarang ke alamat KTP
|
||||
personAddressRelativeForm.value.setValues(
|
||||
{
|
||||
...personAddressRelativeForm.value.values,
|
||||
province_code: newAddressValues.province_code || undefined,
|
||||
regency_code: newAddressValues.regency_code || undefined,
|
||||
district_code: newAddressValues.district_code || undefined,
|
||||
village_code: newAddressValues.village_code || undefined,
|
||||
postalRegion_code: newAddressValues.postalRegion_code || undefined,
|
||||
address: newAddressValues.address || undefined,
|
||||
rt: newAddressValues.rt || undefined,
|
||||
rw: newAddressValues.rw || undefined,
|
||||
},
|
||||
false,
|
||||
)
|
||||
}
|
||||
},
|
||||
{ deep: true },
|
||||
)
|
||||
|
||||
// Watcher untuk memantau perubahan isSameAddress
|
||||
watch(
|
||||
() => personAddressRelativeForm.value?.values?.isSameAddress,
|
||||
(isSameAddress) => {
|
||||
if (
|
||||
(isSameAddress === true || isSameAddress === '1') &&
|
||||
personAddressForm.value?.values &&
|
||||
personAddressRelativeForm.value?.values
|
||||
) {
|
||||
// Ketika isSameAddress diubah menjadi true, copy alamat sekarang ke alamat KTP
|
||||
const currentAddressValues = personAddressForm.value.values
|
||||
personAddressRelativeForm.value.setValues(
|
||||
{
|
||||
...personAddressRelativeForm.value.values,
|
||||
province_code: currentAddressValues.province_code || undefined,
|
||||
regency_code: currentAddressValues.regency_code || undefined,
|
||||
district_code: currentAddressValues.district_code || undefined,
|
||||
village_code: currentAddressValues.village_code || undefined,
|
||||
postalRegion_code: currentAddressValues.postalRegion_code || undefined,
|
||||
address: currentAddressValues.address || undefined,
|
||||
rt: currentAddressValues.rt || undefined,
|
||||
rw: currentAddressValues.rw || undefined,
|
||||
},
|
||||
false,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
// #endregion
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg xl:text-xl">
|
||||
<Icon name="i-lucide-user" class="me-2" />
|
||||
<span class="font-semibold">Tambah</span> Pasien
|
||||
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg font-semibold xl:text-xl">Tambah Pasien</div>
|
||||
<AppPatientEntryForm
|
||||
ref="personPatientForm"
|
||||
:schema="PatientSchema"
|
||||
/>
|
||||
<div class="h-6"></div>
|
||||
<AppPersonAddressEntryForm
|
||||
ref="personAddressForm"
|
||||
title="Alamat Sekarang"
|
||||
:schema="PersonAddressSchema"
|
||||
/>
|
||||
<div class="h-6"></div>
|
||||
<AppPersonAddressEntryFormRelative
|
||||
ref="personAddressRelativeForm"
|
||||
title="Alamat KTP"
|
||||
:schema="PersonAddressRelativeSchema"
|
||||
/>
|
||||
<div class="h-6"></div>
|
||||
<AppPersonFamilyParentsForm
|
||||
ref="personFamilyForm"
|
||||
title="Identitas Orang Tua"
|
||||
:schema="PersonFamiliesSchema"
|
||||
/>
|
||||
<div class="h-6"></div>
|
||||
<AppPersonContactEntryForm
|
||||
ref="personContactForm"
|
||||
title="Kontak Pasien"
|
||||
:contact-limit="10"
|
||||
:schema="PersonContactListSchema"
|
||||
/>
|
||||
<AppPersonRelativeEntryForm
|
||||
ref="personEmergencyContactRelative"
|
||||
title="Penanggung Jawab"
|
||||
:schema="ResponsiblePersonSchema"
|
||||
/>
|
||||
|
||||
<div class="my-2 flex justify-end py-2">
|
||||
<Action @click="handleActionClick" />
|
||||
</div>
|
||||
<AppPatientEntryForm />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* component style */
|
||||
</style>
|
||||
|
||||
@@ -1,309 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { Patient, genPatientProps } from '~/models/patient'
|
||||
import type { ExposedForm } from '~/types/form'
|
||||
import type { PatientBase } from '~/models/patient'
|
||||
import Action from '~/components/pub/my-ui/nav-footer/ba-dr-su.vue'
|
||||
import { genPatient } from '~/models/patient'
|
||||
import { PatientSchema } from '~/schemas/patient.schema'
|
||||
import { PersonAddressRelativeSchema } from '~/schemas/person-address-relative.schema'
|
||||
import { PersonAddressSchema } from '~/schemas/person-address.schema'
|
||||
import { PersonContactListSchema } from '~/schemas/person-contact.schema'
|
||||
import { PersonFamiliesSchema } from '~/schemas/person-family.schema'
|
||||
import { ResponsiblePersonSchema } from '~/schemas/person-relative.schema'
|
||||
import { uploadAttachment } from '~/services/patient.service'
|
||||
|
||||
import {
|
||||
// for form entry
|
||||
isReadonly,
|
||||
isProcessing,
|
||||
isFormEntryDialogOpen,
|
||||
isRecordConfirmationOpen,
|
||||
onResetState,
|
||||
handleActionSave,
|
||||
handleCancelForm,
|
||||
} from '~/handlers/patient.handler'
|
||||
|
||||
import { toast } from '~/components/pub/ui/toast'
|
||||
|
||||
// #region Props & Emits
|
||||
const props = defineProps<{
|
||||
callbackUrl?: string
|
||||
}>()
|
||||
|
||||
const residentIdentityFile = ref<File>()
|
||||
const familyCardFile = ref<File>()
|
||||
|
||||
// form related state
|
||||
const personAddressForm = ref<ExposedForm<any> | null>(null)
|
||||
const personAddressRelativeForm = ref<ExposedForm<any> | null>(null)
|
||||
const personContactForm = ref<ExposedForm<any> | null>(null)
|
||||
const personEmergencyContactRelative = ref<ExposedForm<any> | null>(null)
|
||||
const personFamilyForm = ref<ExposedForm<any> | null>(null)
|
||||
const personPatientForm = ref<ExposedForm<any> | null>(null)
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region State & Computed
|
||||
// #endregion
|
||||
|
||||
// #region Lifecycle Hooks
|
||||
onMounted(() => {
|
||||
// Initial synchronization when forms are mounted and isSameAddress is true by default
|
||||
nextTick(() => {
|
||||
const isSameAddress = personAddressRelativeForm.value?.values?.isSameAddress
|
||||
if (
|
||||
(isSameAddress === true || isSameAddress === '1') &&
|
||||
personAddressForm.value?.values &&
|
||||
personAddressRelativeForm.value
|
||||
) {
|
||||
const currentAddressValues = personAddressForm.value.values
|
||||
if (Object.keys(currentAddressValues).length > 0) {
|
||||
personAddressRelativeForm.value.setValues(
|
||||
{
|
||||
...personAddressRelativeForm.value.values,
|
||||
province_code: currentAddressValues.province_code || undefined,
|
||||
regency_code: currentAddressValues.regency_code || undefined,
|
||||
district_code: currentAddressValues.district_code || undefined,
|
||||
village_code: currentAddressValues.village_code || undefined,
|
||||
postalRegion_code: currentAddressValues.postalRegion_code || undefined,
|
||||
address: currentAddressValues.address || undefined,
|
||||
rt: currentAddressValues.rt || undefined,
|
||||
rw: currentAddressValues.rw || undefined,
|
||||
},
|
||||
false,
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
// #endregion
|
||||
|
||||
// #region Functions
|
||||
async function composeFormData(): Promise<Patient> {
|
||||
const [patient, address, addressRelative, families, contacts, emergencyContact] = await Promise.all([
|
||||
personPatientForm.value?.validate(),
|
||||
personAddressForm.value?.validate(),
|
||||
personAddressRelativeForm.value?.validate(),
|
||||
personFamilyForm.value?.validate(),
|
||||
personContactForm.value?.validate(),
|
||||
personEmergencyContactRelative.value?.validate(),
|
||||
])
|
||||
|
||||
const results = [patient, address, addressRelative, families, contacts, emergencyContact]
|
||||
console.log(results)
|
||||
const allValid = results.every((r) => r?.valid)
|
||||
|
||||
// exit, if form errors happend during validation
|
||||
// for example: dropdown not selected
|
||||
if (!allValid) return Promise.reject('Form validation failed')
|
||||
|
||||
const formDataRequest: genPatientProps = {
|
||||
patient: patient?.values,
|
||||
residentAddress: address?.values,
|
||||
cardAddress: addressRelative?.values,
|
||||
familyData: families?.values,
|
||||
contacts: contacts?.values,
|
||||
responsible: emergencyContact?.values,
|
||||
}
|
||||
|
||||
const formData = genPatient(formDataRequest)
|
||||
|
||||
if (patient?.values.residentIdentityFile) {
|
||||
residentIdentityFile.value = patient?.values.residentIdentityFile
|
||||
}
|
||||
|
||||
if (patient?.values.familyIdentityFile) {
|
||||
familyCardFile.value = patient?.values.familyIdentityFile
|
||||
}
|
||||
|
||||
return new Promise((resolve) => resolve(formData))
|
||||
}
|
||||
// #endregion region
|
||||
|
||||
// #region Utilities & event handlers
|
||||
async function handleActionClick(eventType: string) {
|
||||
if (eventType === 'submit') {
|
||||
const patient: Patient = await composeFormData()
|
||||
let createdPatientId = 0
|
||||
|
||||
const response = await handleActionSave(
|
||||
patient,
|
||||
() => {},
|
||||
() => {},
|
||||
toast,
|
||||
)
|
||||
|
||||
const data = (response?.body?.data ?? null) as PatientBase | null
|
||||
if (!data) return
|
||||
createdPatientId = data.id
|
||||
|
||||
if (residentIdentityFile.value) {
|
||||
void uploadAttachment(residentIdentityFile.value, createdPatientId, 'ktp')
|
||||
}
|
||||
if (familyCardFile.value) {
|
||||
void uploadAttachment(familyCardFile.value, createdPatientId, 'kk')
|
||||
}
|
||||
|
||||
// If has callback provided redirect to callback with patientData
|
||||
if (props.callbackUrl) {
|
||||
await navigateTo(props.callbackUrl + '?patient-id=' + patient.id)
|
||||
return
|
||||
}
|
||||
|
||||
// Navigate to patient list or show success message
|
||||
await navigateTo('/client/patient')
|
||||
return
|
||||
}
|
||||
|
||||
if (eventType === 'cancel') {
|
||||
if (props.callbackUrl) {
|
||||
await navigateTo(props.callbackUrl)
|
||||
return
|
||||
}
|
||||
|
||||
await navigateTo({
|
||||
name: 'client-patient',
|
||||
})
|
||||
// handleCancelForm()
|
||||
}
|
||||
}
|
||||
// #endregion
|
||||
|
||||
// #region Watchers
|
||||
// Watcher untuk sinkronisasi initial ketika kedua form sudah ready
|
||||
watch(
|
||||
[() => personAddressForm.value, () => personAddressRelativeForm.value],
|
||||
([addressForm, relativeForm]) => {
|
||||
if (addressForm && relativeForm) {
|
||||
// Trigger initial sync jika isSameAddress adalah true
|
||||
nextTick(() => {
|
||||
const isSameAddress = relativeForm.values?.isSameAddress
|
||||
if ((isSameAddress === true || isSameAddress === '1') && addressForm.values) {
|
||||
const currentAddressValues = addressForm.values
|
||||
if (Object.keys(currentAddressValues).length > 0) {
|
||||
relativeForm.setValues(
|
||||
{
|
||||
...relativeForm.values,
|
||||
province_code: currentAddressValues.province_code || undefined,
|
||||
regency_code: currentAddressValues.regency_code || undefined,
|
||||
district_code: currentAddressValues.district_code || undefined,
|
||||
village_code: currentAddressValues.village_code || undefined,
|
||||
postalRegion_code: currentAddressValues.postalRegion_code || undefined,
|
||||
address: currentAddressValues.address || undefined,
|
||||
rt: currentAddressValues.rt || undefined,
|
||||
rw: currentAddressValues.rw || undefined,
|
||||
},
|
||||
false,
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
// Watcher untuk sinkronisasi alamat ketika isSameAddress = true
|
||||
watch(
|
||||
() => personAddressForm.value?.values,
|
||||
(newAddressValues) => {
|
||||
// Cek apakah alamat KTP harus sama dengan alamat sekarang
|
||||
const isSameAddress = personAddressRelativeForm.value?.values?.isSameAddress
|
||||
|
||||
if ((isSameAddress === true || isSameAddress === '1') && newAddressValues && personAddressRelativeForm.value) {
|
||||
// Sinkronkan semua field alamat dari alamat sekarang ke alamat KTP
|
||||
personAddressRelativeForm.value.setValues(
|
||||
{
|
||||
...personAddressRelativeForm.value.values,
|
||||
province_code: newAddressValues.province_code || undefined,
|
||||
regency_code: newAddressValues.regency_code || undefined,
|
||||
district_code: newAddressValues.district_code || undefined,
|
||||
village_code: newAddressValues.village_code || undefined,
|
||||
postalRegion_code: newAddressValues.postalRegion_code || undefined,
|
||||
address: newAddressValues.address || undefined,
|
||||
rt: newAddressValues.rt || undefined,
|
||||
rw: newAddressValues.rw || undefined,
|
||||
},
|
||||
false,
|
||||
)
|
||||
}
|
||||
},
|
||||
{ deep: true },
|
||||
)
|
||||
|
||||
// Watcher untuk memantau perubahan isSameAddress
|
||||
watch(
|
||||
() => personAddressRelativeForm.value?.values?.isSameAddress,
|
||||
(isSameAddress) => {
|
||||
if (
|
||||
(isSameAddress === true || isSameAddress === '1') &&
|
||||
personAddressForm.value?.values &&
|
||||
personAddressRelativeForm.value?.values
|
||||
) {
|
||||
// Ketika isSameAddress diubah menjadi true, copy alamat sekarang ke alamat KTP
|
||||
const currentAddressValues = personAddressForm.value.values
|
||||
personAddressRelativeForm.value.setValues(
|
||||
{
|
||||
...personAddressRelativeForm.value.values,
|
||||
province_code: currentAddressValues.province_code || undefined,
|
||||
regency_code: currentAddressValues.regency_code || undefined,
|
||||
district_code: currentAddressValues.district_code || undefined,
|
||||
village_code: currentAddressValues.village_code || undefined,
|
||||
postalRegion_code: currentAddressValues.postalRegion_code || undefined,
|
||||
address: currentAddressValues.address || undefined,
|
||||
rt: currentAddressValues.rt || undefined,
|
||||
rw: currentAddressValues.rw || undefined,
|
||||
},
|
||||
false,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
// #endregion
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg font-semibold xl:text-xl">Tambah Pasien</div>
|
||||
<AppPatientEntryForm
|
||||
ref="personPatientForm"
|
||||
:schema="PatientSchema"
|
||||
/>
|
||||
<div class="h-6"></div>
|
||||
<AppPersonAddressEntryForm
|
||||
ref="personAddressForm"
|
||||
title="Alamat Sekarang"
|
||||
:schema="PersonAddressSchema"
|
||||
/>
|
||||
<div class="h-6"></div>
|
||||
<AppPersonAddressEntryFormRelative
|
||||
ref="personAddressRelativeForm"
|
||||
title="Alamat KTP"
|
||||
:schema="PersonAddressRelativeSchema"
|
||||
/>
|
||||
<div class="h-6"></div>
|
||||
<AppPersonFamilyParentsForm
|
||||
ref="personFamilyForm"
|
||||
title="Identitas Orang Tua"
|
||||
:schema="PersonFamiliesSchema"
|
||||
/>
|
||||
<div class="h-6"></div>
|
||||
<AppPersonContactEntryForm
|
||||
ref="personContactForm"
|
||||
title="Kontak Pasien"
|
||||
:contact-limit="10"
|
||||
:schema="PersonContactListSchema"
|
||||
/>
|
||||
<AppPersonRelativeEntryForm
|
||||
ref="personEmergencyContactRelative"
|
||||
title="Penanggung Jawab"
|
||||
:schema="ResponsiblePersonSchema"
|
||||
/>
|
||||
|
||||
<div class="my-2 flex justify-end py-2">
|
||||
<Action @click="handleActionClick" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* component style */
|
||||
</style>
|
||||
@@ -34,7 +34,7 @@ const callbackUrl = route.query['return-path'] as string | undefined
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="canRead">
|
||||
<ContentPatientEntry :callback-url="callbackUrl" />
|
||||
<ContentPatientAdd :callback-url="callbackUrl" />
|
||||
</div>
|
||||
<Error
|
||||
v-else
|
||||
|
||||
Reference in New Issue
Block a user