wip: ui fix layout

fix(patient-form): add error handling in patient submission and simplify family form

- Wrap patient submission in try-catch to show appropriate error messages
- Simplify family parents form by removing conditional rendering and adding disabled state
- Update form fields to use consistent labels and disable when not sharing family data

feat(family-form): improve family data form handling and UI

- Add edit mode detection to conditionally set default family data
- Restructure form fields display based on shareFamilyData value
- Show disabled placeholder fields when family data is not shared
This commit is contained in:
Khafid Prayoga
2025-11-24 16:06:24 +07:00
parent f3474eb0b5
commit 32654b0b11
2 changed files with 154 additions and 119 deletions
@@ -4,6 +4,7 @@ import { toTypedSchema } from '@vee-validate/zod'
import { FieldArray } from 'vee-validate'
// component
import * as DE from '~/components/pub/my-ui/doc-entry'
import { Form } from '~/components/pub/ui/form'
import { SelectEducation } from '~/components/app/patient/fields'
import { InputBase } from '~/components/pub/my-ui/form'
@@ -17,33 +18,45 @@ const props = defineProps<{
const formSchema = toTypedSchema(props.schema)
const formRef = ref()
const isFamilyFormDisabled = ref(true)
// Watcher untuk mengatur families ketika shareFamilyData berubah
watch(
() => formRef.value?.values?.shareFamilyData,
(newValue) => {
if (newValue === '1' && formRef.value) {
// Ketika memilih "Ya", pastikan ada data families untuk mother dan father
const currentFamilies = formRef.value.values?.families || []
if (currentFamilies.length === 0) {
formRef.value.setFieldValue('families', [
{ relation: 'mother', name: undefined, education: undefined, occupation: undefined },
{ relation: 'father', name: undefined, education: undefined, occupation: undefined },
])
}
} else if (newValue === '0' && formRef.value) {
// Ketika memilih "Tidak", kosongkan families
formRef.value.setFieldValue('families', [])
}
},
{ immediate: false },
)
const isEditing = computed(() => !!props.initialValues?.id)
defineExpose({
validate: () => formRef.value?.validate(),
resetForm: () => formRef.value?.resetForm(),
values: computed(() => formRef.value?.values),
})
watch(
() => formRef.value?.values?.shareFamilyData,
(newValue) => {
if (!formRef.value) return
const currentFamilies = formRef.value.values?.families || []
if (newValue === '1') {
isFamilyFormDisabled.value = false
// CREATE MODE — Auto default
if (!isEditing.value && currentFamilies.length === 0) {
formRef.value.setFieldValue('families', [
{ relation: 'mother', name: '', education: '', occupation: '' },
{ relation: 'father', name: '', education: '', occupation: '' },
])
}
return
}
isFamilyFormDisabled.value = true
if (!isEditing.value) {
formRef.value.setFieldValue('families', [])
}
},
{ immediate: true },
)
</script>
<template>
@@ -72,92 +85,91 @@ defineExpose({
</div>
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg xl:text-xl">
<!-- Radio Button Section - Full Width -->
<div class="mb-6">
<RadioParentsInput field-name="shareFamilyData" />
</div>
<!-- Form Fields Section - Only show when "Ya" is selected -->
<div
v-if="values.shareFamilyData === '1'"
class="space-y-6"
:key="values.shareFamilyData"
>
<FieldArray
v-slot="{ fields }"
name="families"
>
<div class="grid grid-cols-1 gap-6 lg:grid-cols-2">
<template
v-for="(field, idx) in fields"
:key="field.key"
>
<template v-if="values.shareFamilyData === '1'">
<FieldArray
v-slot="{ fields }"
name="families"
>
<div class="grid grid-cols-1 gap-6 lg:grid-cols-2">
<div
v-for="(field, idx) in fields"
:key="field.key"
class="space-y-4 rounded-lg border border-gray-200 bg-gray-50/50 p-4 dark:border-gray-700 dark:bg-gray-800/50"
>
<h4 class="text-sm font-medium text-gray-700 dark:text-gray-300">
{{ (values.families as FamilyData[])?.[idx]?.relation === 'mother' ? 'Data Ibu' : 'Data Ayah' }}
</h4>
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
<div>
<InputBase
:field-name="`families[${idx}].name`"
:label="
(values.families as FamilyData[])?.[idx]?.relation === 'mother'
? 'Nama Ibu Kandung'
: (values.families as FamilyData[])?.[idx]?.relation === 'father'
? 'Nama Ayah Kandung'
: 'Nama Keluarga'
"
:placeholder="
(values.families as FamilyData[])?.[idx]?.relation === 'mother'
? 'Masukkan nama ibu pasien'
: (values.families as FamilyData[])?.[idx]?.relation === 'father'
? 'Masukkan nama ayah pasien'
: 'Masukkan nama'
"
is-required
/>
</div>
<DE.Block>
<InputBase
:field-name="`families[${idx}].name`"
label="Nama"
placeholder="Masukkan nama"
:is-disabled="isFamilyFormDisabled"
/>
<div>
<SelectEducation
:field-name="`families[${idx}].education`"
:label="
(values.families as FamilyData[])?.[idx]?.relation === 'mother'
? 'Pendidikan Ibu'
: (values.families as FamilyData[])?.[idx]?.relation === 'father'
? 'Pendidikan Ayah'
: 'Pendidikan'
"
placeholder="Pilih"
/>
</div>
</div>
<SelectEducation
:field-name="`families[${idx}].education`"
label="Pendidikan"
placeholder="Pilih"
:is-disabled="isFamilyFormDisabled"
/>
<div>
<InputBase
:field-name="`families[${idx}].occupation`"
:label="
(values.families as FamilyData[])?.[idx]?.relation === 'mother'
? 'Pekerjaan Ibu'
: (values.families as FamilyData[])?.[idx]?.relation === 'father'
? 'Pekerjaan Ayah'
: 'Pekerjaan'
"
:placeholder="
(values.families as FamilyData[])?.[idx]?.relation === 'mother'
? 'Masukkan pekerjaan ibu'
: (values.families as FamilyData[])?.[idx]?.relation === 'father'
? 'Masukkan pekerjaan ayah'
: 'Masukkan pekerjaan'
"
label="Pekerjaan"
placeholder="Masukkan pekerjaan"
:is-disabled="isFamilyFormDisabled"
/>
</div>
</DE.Block>
</div>
</template>
</div>
</FieldArray>
</template>
<template v-else>
<div class="grid grid-cols-1 gap-6 lg:grid-cols-2">
<div
v-for="relation in ['mother', 'father']"
:key="relation"
class="space-y-4 rounded-lg border border-gray-200 bg-gray-50/50 p-4 dark:border-gray-700 dark:bg-gray-800/50"
>
<h4 class="text-sm font-medium text-gray-700 dark:text-gray-300">
{{ relation === 'mother' ? 'Data Ibu' : 'Data Ayah' }}
</h4>
<DE.Block>
<InputBase
:field-name="`families[${relation}].name`"
is-disabled
label="Nama"
placeholder="-"
/>
<SelectEducation
:field-name="`families[${relation}].education`"
is-disabled
label="Pendidikan"
placeholder="-"
/>
<InputBase
:field-name="`families[${relation}].occupation`"
is-disabled
label="Pekerjaan"
placeholder="-"
/>
</DE.Block>
</div>
</div>
</FieldArray>
</template>
</div>
</div>
</Form>
+57 -34
View File
@@ -135,49 +135,72 @@ async function composeFormData(): Promise<Patient> {
// #region Utilities & event handlers
async function handleActionClick(eventType: string) {
if (eventType === 'submit') {
const patient: Patient = await composeFormData()
let createdPatientId = 0
try {
if (eventType === 'submit') {
const patient: Patient = await composeFormData()
let createdPatientId = 0
const response = await handleActionSave(
patient,
() => {},
() => {},
toast,
)
const response = await handleActionSave(
patient,
() => {},
() => {},
toast,
)
const data = (response?.body?.data ?? null) as PatientBase | null
if (!data) return
createdPatientId = data.id
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 (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)
// If has callback provided redirect to callback with patientData
if (props.callbackUrl && props.callbackUrl.length > 0) {
await navigateTo(props.callbackUrl + '?patient-id=' + createdPatientId)
return
}
// Navigate to patient list or show success message
await navigateTo('/client/patient')
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
}
if (eventType === 'cancel') {
if (props.callbackUrl) {
await navigateTo(props.callbackUrl)
return
await navigateTo({
name: 'client-patient',
})
// handleCancelForm()
}
} catch (error) {
// Show error toast to user
if (typeof error === 'string') {
toast({
title: 'Error',
description: error,
variant: 'destructive',
})
} else if (error instanceof Error) {
toast({
title: 'Error',
description: error.message || 'Terjadi kesalahan saat menyimpan data',
variant: 'destructive',
})
} else {
toast({
title: 'Error',
description: 'Terjadi kesalahan saat menyimpan data',
variant: 'destructive',
})
}
await navigateTo({
name: 'client-patient',
})
// handleCancelForm()
}
}
// #endregion