feat(patient): add patient handler and refactor form submission
- Introduce new patient handler using genCrudHandler for CRUD operations - Refactor patient entry form to use handler for save operations - Separate form data composition from submission logic - Handle file uploads and navigation after successful submission
This commit is contained in:
@@ -10,14 +10,28 @@ import { PersonAddressSchema } from '~/schemas/person-address.schema'
|
|||||||
import { PersonContactListSchema } from '~/schemas/person-contact.schema'
|
import { PersonContactListSchema } from '~/schemas/person-contact.schema'
|
||||||
import { PersonFamiliesSchema } from '~/schemas/person-family.schema'
|
import { PersonFamiliesSchema } from '~/schemas/person-family.schema'
|
||||||
import { ResponsiblePersonSchema } from '~/schemas/person-relative.schema'
|
import { ResponsiblePersonSchema } from '~/schemas/person-relative.schema'
|
||||||
import { postPatient, uploadAttachment } from '~/services/patient.service'
|
import { uploadAttachment } from '~/services/patient.service'
|
||||||
import { uploadCode } from '~/lib/constants'
|
|
||||||
|
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
|
// #region Props & Emits
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
callbackUrl?: string
|
callbackUrl?: string
|
||||||
}>()
|
}>()
|
||||||
const payload = ref<Patient>()
|
|
||||||
|
const residentIdentityFile = ref<File>()
|
||||||
|
const familyCardFile = ref<File>()
|
||||||
|
|
||||||
// form related state
|
// form related state
|
||||||
const personAddressForm = ref<ExposedForm<any> | null>(null)
|
const personAddressForm = ref<ExposedForm<any> | null>(null)
|
||||||
@@ -65,7 +79,7 @@ onMounted(() => {
|
|||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
// #region Functions
|
// #region Functions
|
||||||
async function sendRequest() {
|
async function composeFormData(): Promise<Patient> {
|
||||||
const [patient, address, addressRelative, families, contacts, emergencyContact] = await Promise.all([
|
const [patient, address, addressRelative, families, contacts, emergencyContact] = await Promise.all([
|
||||||
personPatientForm.value?.validate(),
|
personPatientForm.value?.validate(),
|
||||||
personAddressForm.value?.validate(),
|
personAddressForm.value?.validate(),
|
||||||
@@ -81,7 +95,7 @@ async function sendRequest() {
|
|||||||
|
|
||||||
// exit, if form errors happend during validation
|
// exit, if form errors happend during validation
|
||||||
// for example: dropdown not selected
|
// for example: dropdown not selected
|
||||||
if (!allValid) return
|
if (!allValid) return Promise.reject('Form validation failed')
|
||||||
|
|
||||||
const formDataRequest: genPatientProps = {
|
const formDataRequest: genPatientProps = {
|
||||||
patient: patient?.values,
|
patient: patient?.values,
|
||||||
@@ -93,57 +107,64 @@ async function sendRequest() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const formData = genPatient(formDataRequest)
|
const formData = genPatient(formDataRequest)
|
||||||
console.log(formData)
|
|
||||||
payload.value = formData
|
|
||||||
|
|
||||||
try {
|
if (patient?.values.residentIdentityFile) {
|
||||||
const result = await postPatient(formData)
|
residentIdentityFile.value = patient?.values.residentIdentityFile
|
||||||
const patientData: PatientBase = result.body.data
|
|
||||||
if (result.success) {
|
|
||||||
console.log('Patient created successfully:', patientData)
|
|
||||||
const createdPatientId = patientData.id
|
|
||||||
|
|
||||||
// void run uploadAttachment in background so this try-catch non blocking
|
|
||||||
// behavior: fire-and-forget
|
|
||||||
if (patient?.values.residentIdentityFile) {
|
|
||||||
void uploadAttachment(patient?.values.residentIdentityFile, createdPatientId, 'ktp')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (patient?.values.familyIdentityFile) {
|
|
||||||
void uploadAttachment(patient?.values.familyIdentityFile, createdPatientId, 'kk')
|
|
||||||
}
|
|
||||||
// 30s
|
|
||||||
await new Promise((r) => setTimeout(r, 30_000))
|
|
||||||
|
|
||||||
// If has callback provided redirect to callback with patientData
|
|
||||||
if (props.callbackUrl) {
|
|
||||||
await navigateTo(props.callbackUrl + '?patient-id=' + patientData.id)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Navigate to patient list or show success message
|
|
||||||
await navigateTo('/client/patient')
|
|
||||||
} else {
|
|
||||||
console.error('Failed to create patient:', result)
|
|
||||||
// Handle error - show error message to user
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error creating patient:', error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (patient?.values.familyIdentityFile) {
|
||||||
|
familyCardFile.value = patient?.values.familyIdentityFile
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve) => resolve(formData))
|
||||||
}
|
}
|
||||||
// #endregion region
|
// #endregion region
|
||||||
|
|
||||||
// #region Utilities & event handlers
|
// #region Utilities & event handlers
|
||||||
async function handleActionClick(eventType: string) {
|
async function handleActionClick(eventType: string) {
|
||||||
if (eventType === 'submit') {
|
if (eventType === 'submit') {
|
||||||
await sendRequest()
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (eventType === 'cancel') {
|
if (eventType === 'cancel') {
|
||||||
navigateTo({
|
if (props.callbackUrl) {
|
||||||
|
await navigateTo(props.callbackUrl)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await navigateTo({
|
||||||
name: 'client-patient',
|
name: 'client-patient',
|
||||||
})
|
})
|
||||||
|
// handleCancelForm()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|||||||
@@ -129,23 +129,34 @@ export function genCrudHandler<T = any>(crud: {
|
|||||||
recItem.value = null
|
recItem.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleActionSave(values: any, refresh: () => void, reset: () => void, toast: ToastFn) {
|
async function handleActionSave(
|
||||||
|
values: any,
|
||||||
|
refresh: () => void,
|
||||||
|
reset: () => void,
|
||||||
|
toast: ToastFn,
|
||||||
|
): Promise<any | null> {
|
||||||
isProcessing.value = true
|
isProcessing.value = true
|
||||||
|
|
||||||
|
let successResponse: any = null
|
||||||
|
|
||||||
await handleAsyncAction<[any], any>({
|
await handleAsyncAction<[any], any>({
|
||||||
action: crud.create,
|
action: crud.create,
|
||||||
args: [values],
|
args: [values],
|
||||||
toast,
|
toast,
|
||||||
successMessage: 'Data berhasil disimpan',
|
successMessage: 'Data berhasil disimpan',
|
||||||
errorMessage: 'Gagal menyimpan data',
|
errorMessage: 'Gagal menyimpan data',
|
||||||
onSuccess: () => {
|
onSuccess: (result) => {
|
||||||
isFormEntryDialogOpen.value = false
|
isFormEntryDialogOpen.value = false
|
||||||
if (refresh) refresh()
|
if (refresh) refresh()
|
||||||
|
successResponse = result
|
||||||
},
|
},
|
||||||
onFinally: (isSuccess: boolean) => {
|
onFinally: (isSuccess: boolean) => {
|
||||||
if (isSuccess) setTimeout(reset, 500)
|
if (isSuccess) setTimeout(reset, 500)
|
||||||
isProcessing.value = false
|
isProcessing.value = false
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return successResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleActionEdit(
|
async function handleActionEdit(
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// Handlers
|
||||||
|
import { genCrudHandler } from '~/handlers/_handler'
|
||||||
|
|
||||||
|
// Services
|
||||||
|
import { postPatient as create, patchPatient as update, removePatient as remove } from '~/services/patient.service'
|
||||||
|
|
||||||
|
export const {
|
||||||
|
recId,
|
||||||
|
recAction,
|
||||||
|
recItem,
|
||||||
|
isReadonly,
|
||||||
|
isProcessing,
|
||||||
|
isFormEntryDialogOpen,
|
||||||
|
isRecordConfirmationOpen,
|
||||||
|
onResetState,
|
||||||
|
handleActionSave,
|
||||||
|
handleActionEdit,
|
||||||
|
handleActionRemove,
|
||||||
|
handleCancelForm,
|
||||||
|
} = genCrudHandler({
|
||||||
|
create,
|
||||||
|
update,
|
||||||
|
remove,
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user