feat(encounter): enhance entry form with submit method and save functionality
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user