feat(encounter): enhance entry form with submit method and save functionality

This commit is contained in:
riefive
2025-11-09 17:43:55 +07:00
parent 5b15f86acb
commit 79e1d54710
2 changed files with 201 additions and 30 deletions
+14 -28
View File
@@ -131,11 +131,25 @@ const onSubmit = handleSubmit((values) => {
console.log('✅ Validated form values:', JSON.stringify(values, null, 2))
emit('event', 'save', values)
})
// Expose submit method for parent component
const formRef = ref<HTMLFormElement | null>(null)
function submitForm() {
if (formRef.value) {
formRef.value.requestSubmit()
}
}
defineExpose({
submitForm,
})
</script>
<template>
<div class="mx-auto w-full">
<form
ref="formRef"
@submit.prevent="onSubmit"
class="grid gap-6 p-4"
>
@@ -411,34 +425,6 @@ const onSubmit = handleSubmit((values) => {
/>
</Block>
</template>
<!-- Actions -->
<div class="mt-6 flex justify-end gap-2">
<Button
variant="outline"
type="button"
class="h-[40px] min-w-[120px] rounded-md border-orange-400 text-orange-400 hover:bg-green-50 hover:text-orange-400"
@click="emit('event', 'cancel')"
>
<Icon
name="i-lucide-x"
class="h-5 w-5"
/>
Batal
</Button>
<Button
type="button"
class="h-[40px] min-w-[120px] text-white"
:disabled="isLoading"
@click="onSubmit"
>
<Icon
name="i-lucide-save"
class="h-5 w-5"
/>
Simpan
</Button>
</div>
</form>
</div>
</template>
+187 -2
View File
@@ -1,5 +1,7 @@
<script setup lang="ts">
// Components
import { toast } from '~/components/pub/ui/toast'
import { Button } from '~/components/pub/ui/button'
import AppEncounterEntryForm from '~/components/app/encounter/entry-form.vue'
import AppViewPatient from '~/components/app/patient/view-patient.vue'
@@ -16,6 +18,7 @@ import {
getValueTreeItems as getSpecialistTreeItems,
} from '~/services/specialist.service'
import { getValueLabelList as getDoctorValueLabelList } from '~/services/doctor.service'
import { create as createEncounter } from '~/services/encounter.service'
// Handlers
import {
@@ -28,6 +31,11 @@ import {
getPatientByIdentifierSearch,
} from '~/handlers/patient.handler'
// Stores
import { useUserStore } from '~/stores/user'
const props = defineProps<{
id: number
classCode?: 'ambulatory' | 'emergency' | 'inpatient'
@@ -36,6 +44,7 @@ const props = defineProps<{
}>()
const route = useRoute()
const userStore = useUserStore()
const openPatient = ref(false)
const isLoading = reactive<DataTableLoader>({
isTableLoading: false,
@@ -44,8 +53,16 @@ const paymentsList = ref<Array<{ value: string; label: string }>>([])
const sepsList = ref<Array<{ value: string; label: string }>>([])
const participantGroupsList = ref<Array<{ value: string; label: string }>>([])
const specialistsTree = ref<TreeItem[]>([])
const specialistsData = ref<any[]>([]) // Store full specialist data with id
const doctorsList = ref<Array<{ value: string; label: string }>>([])
const recSelectId = ref<number | null>(null)
const isSaving = ref(false)
const formRef = ref<InstanceType<typeof AppEncounterEntryForm> | null>(null)
// Computed for save button disable state
const isSaveDisabled = computed(() => {
return !selectedPatient.value || !selectedPatientObject.value || isSaving.value
})
function handleSavePatient() {
selectedPatientObject.value = null
@@ -77,7 +94,104 @@ function toNavigateSep(values: any) {
navigateTo('/integration/bpjs/sep/add' + `?${queryParams.toString()}`)
}
function handleEvent(menu: string, value?: any) {
async function handleSaveEncounter(formValues: any) {
// Validate patient is selected
if (!selectedPatient.value || !selectedPatientObject.value) {
toast({
title: 'Gagal',
description: 'Pasien harus dipilih terlebih dahulu',
variant: 'destructive',
})
return
}
try {
isSaving.value = true
// Get employee_id from user store
const employeeId = userStore.user?.employee_id || userStore.user?.employee?.id || 0
// Format date to ISO format
const formatDate = (dateString: string): string => {
if (!dateString) return ''
const date = new Date(dateString)
return date.toISOString()
}
// Get specialist_id and subspecialist_id from TreeSelect value (code)
const { specialist_id, subspecialist_id } = getSpecialistIdsFromCode(formValues.subSpecialistId || '')
// Build payload
const payload: any = {
patient_id: selectedPatientObject.value?.id || Number(selectedPatient.value),
registeredAt: formatDate(formValues.registerDate),
visitDate: formatDate(formValues.registerDate),
class_code: props.classCode || '',
subClass_code: props.subClassCode || '',
infra_id: 0,
unit_id: 0,
appointment_doctor_id: Number(formValues.doctorId),
responsible_doctor_id: Number(formValues.doctorId),
paymentType: formValues.paymentType,
cardNumber: formValues.cardNumber,
adm_employee_id: employeeId,
refSource_name: '',
appointment_id: null,
}
// Add specialist_id and subspecialist_id if available
if (specialist_id) {
payload.specialist_id = specialist_id
}
if (subspecialist_id) {
payload.subspecialist_id = subspecialist_id
}
if (formValues.paymentType === 'jkn') {
payload.paymentMethod_code = 'insurance'
payload.insuranceCompany_id = 0
payload.member_number = formValues.cardNumber
payload.ref_number = formValues.sepNumber
}
// Add visitMode_code and allocatedVisitCount only if classCode is ambulatory
if (props.classCode === 'ambulatory') {
payload.visitMode_code = 'adm'
payload.allocatedVisitCount = 0
}
// Call encounter service
const result = await createEncounter(payload)
if (result.success) {
toast({
title: 'Berhasil',
description: 'Kunjungan berhasil dibuat',
variant: 'default',
})
// Optionally navigate or reset form
// navigateTo(`/encounter/${result.body?.data?.id}`)
} else {
const errorMessage = result.body?.message || 'Gagal membuat kunjungan'
toast({
title: 'Gagal',
description: errorMessage,
variant: 'destructive',
})
}
} catch (error: any) {
console.error('Error saving encounter:', error)
toast({
title: 'Gagal',
description: error?.message || 'Gagal membuat kunjungan',
variant: 'destructive',
})
} finally {
isSaving.value = false
}
}
async function handleEvent(menu: string, value?: any) {
if (menu === 'search') {
getPatientsList({ 'page-size': 10, includes: 'person' }).then(() => {
openPatient.value = true
@@ -93,7 +207,7 @@ function handleEvent(menu: string, value?: any) {
...value,
})
} else if (menu === 'save') {
console.log('Save encounter:', value)
await handleSaveEncounter(value)
} else if (menu === 'cancel') {
console.log('Cancel')
}
@@ -104,6 +218,7 @@ async function handleFetchSpecialists() {
const specialistsResult = await getSpecialistList({ 'page-size': 100, includes: 'subspecialists' })
if (specialistsResult.success) {
const specialists = specialistsResult.body?.data || []
specialistsData.value = specialists // Store full data for mapping
specialistsTree.value = getSpecialistTreeItems(specialists)
}
} catch (error) {
@@ -134,6 +249,47 @@ function isSubspecialist(value: string, items: TreeItem[]): boolean {
return false
}
/**
* Helper function to find specialist_id and subspecialist_id from TreeSelect value (code)
* Returns { specialist_id: number | null, subspecialist_id: number | null }
*/
function getSpecialistIdsFromCode(code: string): { specialist_id: number | null; subspecialist_id: number | null } {
if (!code) {
return { specialist_id: null, subspecialist_id: null }
}
// Check if it's a subspecialist
const isSub = isSubspecialist(code, specialistsTree.value)
if (isSub) {
// Find subspecialist and its parent specialist
for (const specialist of specialistsData.value) {
if (specialist.subspecialists && Array.isArray(specialist.subspecialists)) {
for (const subspecialist of specialist.subspecialists) {
if (subspecialist.code === code) {
return {
specialist_id: specialist.id ? Number(specialist.id) : null,
subspecialist_id: subspecialist.id ? Number(subspecialist.id) : null,
}
}
}
}
}
} else {
// It's a specialist
for (const specialist of specialistsData.value) {
if (specialist.code === code) {
return {
specialist_id: specialist.id ? Number(specialist.id) : null,
subspecialist_id: null,
}
}
}
}
return { specialist_id: null, subspecialist_id: null }
}
async function handleFetchDoctors(subSpecialistId: string | null = null) {
try {
// Build filter based on selection type
@@ -207,6 +363,7 @@ onMounted(async () => {
</div>
<AppEncounterEntryForm
ref="formRef"
:payments="paymentsList"
:seps="sepsList"
:participant-groups="participantGroupsList"
@@ -235,4 +392,32 @@ onMounted(async () => {
"
@save="handleSavePatient"
/>
<!-- Footer Actions -->
<div class="mt-6 flex justify-end gap-2 border-t border-t-slate-300 pt-4">
<Button
variant="outline"
type="button"
class="h-[40px] min-w-[120px] rounded-md border-orange-400 text-orange-400 hover:bg-green-50 hover:text-orange-400"
@click="handleEvent('cancel')"
>
<Icon
name="i-lucide-x"
class="h-5 w-5"
/>
Batal
</Button>
<Button
type="button"
class="h-[40px] min-w-[120px] text-white"
:disabled="isSaveDisabled"
@click="formRef?.submitForm()"
>
<Icon
name="i-lucide-save"
class="h-5 w-5"
/>
Simpan
</Button>
</div>
</template>