From b52f438b23057a518ad2de2cd52a3f1e370ef803 Mon Sep 17 00:00:00 2001 From: riefive Date: Mon, 10 Nov 2025 12:44:27 +0700 Subject: [PATCH] feat(encounter): implement encounter detail loading and update handling in entry form --- app/components/content/encounter/entry.vue | 165 +++++++++++++++++- app/components/content/encounter/list.vue | 81 ++++++--- .../emergency/encounter/[id]/edit.vue | 56 ++++++ .../inpatient/encounter/[id]/edit.vue | 56 ++++++ .../outpatient/encounter/[id]/edit.vue | 56 ++++++ .../(features)/rehab/encounter/[id]/edit.vue | 26 ++- 6 files changed, 404 insertions(+), 36 deletions(-) create mode 100644 app/pages/(features)/emergency/encounter/[id]/edit.vue create mode 100644 app/pages/(features)/inpatient/encounter/[id]/edit.vue create mode 100644 app/pages/(features)/outpatient/encounter/[id]/edit.vue diff --git a/app/components/content/encounter/entry.vue b/app/components/content/encounter/entry.vue index 75f958be..a1919b0b 100644 --- a/app/components/content/encounter/entry.vue +++ b/app/components/content/encounter/entry.vue @@ -18,7 +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' +import { create as createEncounter, getDetail as getEncounterDetail, update as updateEncounter } from '~/services/encounter.service' // Handlers import { @@ -55,11 +55,17 @@ const specialistsData = ref([]) // Store full specialist data with id const doctorsList = ref>([]) const recSelectId = ref(null) const isSaving = ref(false) +const isLoadingDetail = ref(false) const formRef = ref | null>(null) +const encounterData = ref(null) +const formObjects = ref({}) + +// Computed for edit mode +const isEditMode = computed(() => props.id > 0) // Computed for save button disable state const isSaveDisabled = computed(() => { - return !selectedPatient.value || !selectedPatientObject.value || isSaving.value + return !selectedPatient.value || !selectedPatientObject.value || isSaving.value || isLoadingDetail.value }) function getListPath(): string { @@ -186,19 +192,24 @@ async function handleSaveEncounter(formValues: any) { payload.allocatedVisitCount = 0 } - // Call encounter service - const result = await createEncounter(payload) + // Call encounter service - use update if edit mode, create otherwise + let result + if (isEditMode.value) { + result = await updateEncounter(props.id, payload) + } else { + result = await createEncounter(payload) + } if (result.success) { toast({ title: 'Berhasil', - description: 'Kunjungan berhasil dibuat', + description: isEditMode.value ? 'Kunjungan berhasil diperbarui' : 'Kunjungan berhasil dibuat', variant: 'default', }) // Redirect to list page await navigateTo(getListPath()) } else { - const errorMessage = result.body?.message || 'Gagal membuat kunjungan' + const errorMessage = result.body?.message || (isEditMode.value ? 'Gagal memperbarui kunjungan' : 'Gagal membuat kunjungan') toast({ title: 'Gagal', description: errorMessage, @@ -209,7 +220,7 @@ async function handleSaveEncounter(formValues: any) { console.error('Error saving encounter:', error) toast({ title: 'Gagal', - description: error?.message || 'Gagal membuat kunjungan', + description: error?.message || (isEditMode.value ? 'Gagal memperbarui kunjungan' : 'Gagal membuat kunjungan'), variant: 'destructive', }) } finally { @@ -370,11 +381,149 @@ async function handleInit() { await handleFetchSpecialists() } +/** + * Load encounter detail data for edit mode + */ +async function loadEncounterDetail() { + if (!isEditMode.value || props.id <= 0) { + return + } + + try { + isLoadingDetail.value = true + const result = await getEncounterDetail(props.id, { + includes: 'patient,patient-person,specialist,subspecialist,appointment_doctor,responsible_doctor,encounter_payments', + }) + + if (result.success && result.body?.data) { + encounterData.value = result.body.data + await mapEncounterToForm(encounterData.value) + } else { + toast({ + title: 'Gagal', + description: 'Gagal memuat data kunjungan', + variant: 'destructive', + }) + // Redirect to list page if encounter not found + await navigateTo(getListPath()) + } + } catch (error: any) { + console.error('Error loading encounter detail:', error) + toast({ + title: 'Gagal', + description: error?.message || 'Gagal memuat data kunjungan', + variant: 'destructive', + }) + // Redirect to list page on error + await navigateTo(getListPath()) + } finally { + isLoadingDetail.value = false + } +} + +/** + * Map encounter data to form fields + */ +async function mapEncounterToForm(encounter: any) { + if (!encounter) return + + // Set patient data and wait for it to load + if (encounter.patient) { + selectedPatient.value = String(encounter.patient.id) + selectedPatientObject.value = encounter.patient + // Fetch full patient data to ensure we have all fields + await getPatientCurrent(selectedPatient.value) + } + + // Map form fields + const formData: any = {} + + // Patient data (readonly, populated from selected patient) + // Use selectedPatientObject which now has full patient data + if (selectedPatientObject.value?.person) { + formData.patientName = selectedPatientObject.value.person.name || '' + formData.nationalIdentity = selectedPatientObject.value.person.residentIdentityNumber || '' + formData.medicalRecordNumber = selectedPatientObject.value.number || '' + } else if (encounter.patient?.person) { + // Fallback to encounter patient data if selectedPatientObject is not yet loaded + formData.patientName = encounter.patient.person.name || '' + formData.nationalIdentity = encounter.patient.person.residentIdentityNumber || '' + formData.medicalRecordNumber = encounter.patient.number || '' + } + + // Doctor ID + const doctorId = encounter.appointment_doctor_id || encounter.responsible_doctor_id + if (doctorId) { + formData.doctorId = String(doctorId) + } + + // Specialist/Subspecialist + if (encounter.subspecialist?.code) { + formData.subSpecialistId = encounter.subspecialist.code + } else if (encounter.specialist?.code) { + formData.subSpecialistId = encounter.specialist.code + } + + // Register date + if (encounter.registeredAt) { + // Convert ISO date to local date string (YYYY-MM-DD) + const date = new Date(encounter.registeredAt) + formData.registerDate = date.toISOString().split('T')[0] + } else if (encounter.visitDate) { + const date = new Date(encounter.visitDate) + formData.registerDate = date.toISOString().split('T')[0] + } + + // Payment data + // Check if encounter has payment data + if (encounter.encounter_payments && Array.isArray(encounter.encounter_payments) && encounter.encounter_payments.length > 0) { + const payment = encounter.encounter_payments[0] + + // Determine payment type from paymentMethod_code + if (payment.paymentMethod_code === 'insurance') { + // Check if it's JKN or JKMM based on member_number or other indicators + // For now, default to 'jkn' - this might need adjustment based on actual data structure + formData.paymentType = 'jkn' + formData.cardNumber = payment.member_number || '' + formData.sepNumber = payment.ref_number || '' + // Note: patientCategory and sepType might need to be extracted from other sources + // as they might not be directly in the payment object + } else { + // For non-insurance payments, try to determine from encounter data + // This might need adjustment based on actual API response + formData.paymentType = 'spm' // default to SPM + } + } else { + // Fallback: try to get payment type from encounter.paymentType if available + if (encounter.paymentType) { + formData.paymentType = encounter.paymentType + } + if (encounter.cardNumber) { + formData.cardNumber = encounter.cardNumber + } + if (encounter.ref_number || encounter.sepNumber) { + formData.sepNumber = encounter.ref_number || encounter.sepNumber + } + } + + // Set form objects for the form component + formObjects.value = formData + + // Fetch doctors based on specialist/subspecialist selection + if (formData.subSpecialistId) { + await handleFetchDoctors(formData.subSpecialistId) + } +} + provide('rec_select_id', recSelectId) provide('table_data_loader', isLoading) onMounted(async () => { await handleInit() + // Load encounter detail if in edit mode + if (isEditMode.value) { + await loadEncounterDetail() + } }) @@ -396,6 +545,8 @@ onMounted(async () => { :specialists="specialistsTree" :doctor="doctorsList" :patient="selectedPatientObject" + :objects="formObjects" + :is-loading="isLoadingDetail" @event="handleEvent" @fetch="handleFetch" /> diff --git a/app/components/content/encounter/list.vue b/app/components/content/encounter/list.vue index 0dd9a3bd..655a553d 100644 --- a/app/components/content/encounter/list.vue +++ b/app/components/content/encounter/list.vue @@ -74,6 +74,25 @@ const refSearchNav: RefSearchNav = { // Loading state management +/** + * Get base path for encounter routes based on classCode and subClassCode + */ +function getBasePath(): string { + if (props.classCode === 'ambulatory' && props.subClassCode === 'rehab') { + return '/rehab/encounter' + } + if (props.classCode === 'ambulatory' && props.subClassCode === 'reg') { + return '/outpatient/encounter' + } + if (props.classCode === 'emergency') { + return '/emergency/encounter' + } + if (props.classCode === 'inpatient') { + return '/inpatient/encounter' + } + return '/encounter' // fallback +} + async function getPatientList() { isLoading.isTableLoading = true try { @@ -81,6 +100,9 @@ async function getPatientList() { if (props.classCode) { params['class-code'] = props.classCode } + if (props.subClassCode) { + params['sub-class-code'] = props.subClassCode + } const result = await getEncounterList(params) if (result.success) { data.value = result.body?.data || [] @@ -144,27 +166,31 @@ watch( isRecordConfirmationOpen.value = true return } - // if (props.type === 'encounter') { - // if (recAction.value === 'showDetail') { - // navigateTo(`/rehab/encounter/${recId.value}/detail`) - // } else if (recAction.value === 'showEdit') { - // navigateTo(`/rehab/encounter/${recId.value}/edit`) - // } else if (recAction.value === 'showProcess') { - // navigateTo(`/rehab/encounter/${recId.value}/process`) - // } else { - // // handle other actions - // } - // } else if (props.type === 'registration') { - // if (recAction.value === 'showDetail') { - // navigateTo(`/rehab/registration/${recId.value}/detail`) - // } else if (recAction.value === 'showEdit') { - // navigateTo(`/rehab/registration/${recId.value}/edit`) - // } else if (recAction.value === 'showProcess') { - // navigateTo(`/rehab/registration/${recId.value}/process`) - // } else { - // // handle other actions - // } - // } + + const basePath = getBasePath() + + if (props.type === 'encounter') { + if (recAction.value === 'showDetail') { + navigateTo(`${basePath}/${recId.value}/detail`) + } else if (recAction.value === 'showEdit') { + navigateTo(`${basePath}/${recId.value}/edit`) + } else if (recAction.value === 'showProcess') { + navigateTo(`${basePath}/${recId.value}/process`) + } else { + // handle other actions + } + } else if (props.type === 'registration') { + // Handle registration type if needed + if (recAction.value === 'showDetail') { + navigateTo(`${basePath.replace('/encounter', '/registration')}/${recId.value}/detail`) + } else if (recAction.value === 'showEdit') { + navigateTo(`${basePath.replace('/encounter', '/registration')}/${recId.value}/edit`) + } else if (recAction.value === 'showProcess') { + navigateTo(`${basePath.replace('/encounter', '/registration')}/${recId.value}/process`) + } else { + // handle other actions + } + } }, ) @@ -185,7 +211,10 @@ onMounted(() => { /> - + @@ -195,7 +224,13 @@ onMounted(() => { size="lg" prevent-outside > - + diff --git a/app/pages/(features)/emergency/encounter/[id]/edit.vue b/app/pages/(features)/emergency/encounter/[id]/edit.vue new file mode 100644 index 00000000..92d263d7 --- /dev/null +++ b/app/pages/(features)/emergency/encounter/[id]/edit.vue @@ -0,0 +1,56 @@ + + + + diff --git a/app/pages/(features)/inpatient/encounter/[id]/edit.vue b/app/pages/(features)/inpatient/encounter/[id]/edit.vue new file mode 100644 index 00000000..2df3d4f6 --- /dev/null +++ b/app/pages/(features)/inpatient/encounter/[id]/edit.vue @@ -0,0 +1,56 @@ + + + + diff --git a/app/pages/(features)/outpatient/encounter/[id]/edit.vue b/app/pages/(features)/outpatient/encounter/[id]/edit.vue new file mode 100644 index 00000000..9713675f --- /dev/null +++ b/app/pages/(features)/outpatient/encounter/[id]/edit.vue @@ -0,0 +1,56 @@ + + + + diff --git a/app/pages/(features)/rehab/encounter/[id]/edit.vue b/app/pages/(features)/rehab/encounter/[id]/edit.vue index 77b0dd3e..02dc5428 100644 --- a/app/pages/(features)/rehab/encounter/[id]/edit.vue +++ b/app/pages/(features)/rehab/encounter/[id]/edit.vue @@ -6,7 +6,7 @@ import { PAGE_PERMISSIONS } from '~/lib/page-permission' definePageMeta({ middleware: ['rbac'], roles: ['doctor', 'nurse', 'admisi', 'pharmacy', 'billing', 'management'], - title: 'Tambah Kunjungan', + title: 'Edit Kunjungan', contentFrame: 'cf-full-width', }) @@ -18,7 +18,7 @@ useHead({ const roleAccess: PagePermission = PAGE_PERMISSIONS['/rehab/encounter'] -const { checkRole, hasCreateAccess } = useRBAC() +const { checkRole, hasUpdateAccess } = useRBAC() // Check if user has access to this page const hasAccess = checkRole(roleAccess) @@ -30,12 +30,26 @@ if (!hasAccess) { } // Define permission-based computed properties -const canCreate = hasCreateAccess(roleAccess) +const canUpdate = hasUpdateAccess(roleAccess) + +// Get encounter ID from route params +const encounterId = computed(() => { + const id = route.params.id + return typeof id === 'string' ? parseInt(id) : 0 +})