From 840efeba8c9fb04a8cb03f71a3597ea6f32df5b3 Mon Sep 17 00:00:00 2001 From: riefive Date: Fri, 14 Nov 2025 15:02:49 +0700 Subject: [PATCH] fix: improves encounter detail rendering and data mapping --- .../app/encounter/quick-info-full.vue | 170 ++++++++++-------- app/components/content/encounter/home.vue | 130 +++++++++++--- app/models/encounter.ts | 1 + .../outpatient/encounter/[id]/process.vue | 44 +++++ 4 files changed, 240 insertions(+), 105 deletions(-) create mode 100644 app/pages/(features)/outpatient/encounter/[id]/process.vue diff --git a/app/components/app/encounter/quick-info-full.vue b/app/components/app/encounter/quick-info-full.vue index 52e26e07..4c1f3861 100644 --- a/app/components/app/encounter/quick-info-full.vue +++ b/app/components/app/encounter/quick-info-full.vue @@ -75,7 +75,7 @@ const genderLabel = computed(() => { const paymentTypeLabel = computed(() => { const code = props.data.paymentMethod_code if (!code) return '-' - + // Map payment method codes if (code === 'insurance') { return 'JKN' @@ -88,12 +88,12 @@ const paymentTypeLabel = computed(() => { } else if (code === 'pks') { return 'PKS' } - + // Try to get from paymentTypes constant if (paymentTypes[code]) { return paymentTypes[code].split(' ')[0] // Get first part (e.g., "JKN" from "JKN (Jaminan...)") } - + return code }) @@ -138,91 +138,103 @@ const bedNumber = computed(() => { - diff --git a/app/components/content/encounter/home.vue b/app/components/content/encounter/home.vue index e09d7630..6efce7d4 100644 --- a/app/components/content/encounter/home.vue +++ b/app/components/content/encounter/home.vue @@ -6,6 +6,7 @@ import { getDetail } from '~/services/encounter.service' import { getPositionAs } from '~/lib/roles' import type { TabItem } from '~/components/pub/my-ui/comp-tab/type' +import EncounterQuickInfoFull from '~/components/app/encounter/quick-info-full.vue' import CompMenu from '~/components/pub/my-ui/comp-menu/comp-menu.vue' import CompTab from '~/components/pub/my-ui/comp-tab/comp-tab.vue' @@ -43,32 +44,80 @@ const activeTab = computed({ }) const id = typeof route.params.id == 'string' ? parseInt(route.params.id) : 0 -// const dataRes = await getDetail(id, { -// includes: -// 'patient,patient-person,patient-person-addresses,unit,Appointment_Doctor,Appointment_Doctor-employee,Appointment_Doctor-employee-person', -// }) -// const dataResBody = dataRes.body ?? null -// const data = dataResBody?.data ?? null -// Dummy data so AppEncounterQuickInfo can render in development/storybook -// Replace with real API result when available (see commented fetch below) -const data = ref({ - patient: { - number: 'RM-2025-0001', - person: { - name: 'John Doe', - birthDate: '1980-01-01T00:00:00Z', - gender_code: 'M', - addresses: [{ address: 'Jl. Contoh No.1, Jakarta' }], - frontTitle: '', - endTitle: '', +const data = ref(null) + +// Function to check if date is invalid (like "0001-01-01T00:00:00Z") +function isValidDate(dateString: string | null | undefined): boolean { + if (!dateString) return false + // Check for invalid date patterns + if (dateString.startsWith('0001-01-01')) return false + try { + const date = new Date(dateString) + return !isNaN(date.getTime()) + } catch { + return false + } +} + +// Function to map API response to Encounter structure +function mapApiResponseToEncounter(apiResponse: any): any { + if (!apiResponse) return null + + // Check if patient and patient.person exist (minimal validation) + if (!apiResponse.patient || !apiResponse.patient.person) { + return null + } + + const mapped: any = { + id: apiResponse.id || 0, + patient_id: apiResponse.patient_id || apiResponse.patient?.id || 0, + patient: { + id: apiResponse.patient?.id || 0, + number: apiResponse.patient?.number || '', + person: { + id: apiResponse.patient?.person?.id || 0, + name: apiResponse.patient?.person?.name || '', + birthDate: apiResponse.patient?.person?.birthDate || null, + gender_code: apiResponse.patient?.person?.gender_code || '', + residentIdentityNumber: apiResponse.patient?.person?.residentIdentityNumber || null, + frontTitle: apiResponse.patient?.person?.frontTitle || '', + endTitle: apiResponse.patient?.person?.endTitle || '', + addresses: apiResponse.patient?.person?.addresses || [], + }, }, - }, - visitDate: new Date().toISOString(), - unit: { name: 'Onkologi' }, - responsible_doctor: null, - appointment_doctor: { employee: { person: { name: 'Dr. Clara Smith', frontTitle: 'Dr.', endTitle: 'Sp.OG' } } }, -}) + registeredAt: apiResponse.registeredAt || apiResponse.patient?.registeredAt || null, + class_code: apiResponse.class_code || '', + unit_id: apiResponse.unit_id || 0, + unit: apiResponse.unit || null, + specialist_id: apiResponse.specialist_id || null, + subspecialist_id: apiResponse.subspecialist_id || null, + visitDate: isValidDate(apiResponse.visitDate) ? apiResponse.visitDate : (apiResponse.registeredAt || apiResponse.patient?.registeredAt || null), + adm_employee_id: apiResponse.adm_employee_id || 0, + appointment_doctor_id: apiResponse.appointment_doctor_id || null, + responsible_doctor_id: apiResponse.responsible_doctor_id || null, + appointment_doctor: apiResponse.appointment_doctor || null, + responsible_doctor: apiResponse.responsible_doctor || null, + refSource_name: apiResponse.refSource_name || null, + appointment_id: apiResponse.appointment_id || null, + earlyEducation: apiResponse.earlyEducation || null, + medicalDischargeEducation: apiResponse.medicalDischargeEducation || '', + admDischargeEducation: apiResponse.admDischargeEducation || null, + discharge_method_code: apiResponse.discharge_method_code || null, + discharge_reason: apiResponse.dischargeReason || apiResponse.discharge_reason || null, + discharge_date: apiResponse.discharge_date || null, + status_code: apiResponse.status_code || '', + // Payment related fields + paymentMethod_code: apiResponse.paymentMethod_code && apiResponse.paymentMethod_code.trim() !== '' + ? apiResponse.paymentMethod_code + : null, + trx_number: apiResponse.trx_number || null, + member_number: apiResponse.member_number || null, + ref_number: apiResponse.ref_number || null, + } + + return mapped +} // Dummy rows for ProtocolList (matches keys expected by list-cfg.protocol) const protocolRows = [ @@ -289,6 +338,31 @@ const tabsRaws: TabItem[] = [ }, ] +async function getData() { + try { + const dataRes = await getDetail(id, { + includes: + 'patient,patient-person,patient-person-addresses,unit,Appointment_Doctor,Appointment_Doctor-employee,Appointment_Doctor-employee-person,Responsible_Doctor,Responsible_Doctor-employee,Responsible_Doctor-employee-person', + }) + const dataResBody = dataRes.body ?? null + const result = dataResBody?.data ?? null + + if (result) { + const mappedData = mapApiResponseToEncounter(result) + if (mappedData) { + data.value = mappedData + } else { + data.value = null + } + } else { + data.value = null + } + } catch (error) { + console.error('Error fetching encounter data:', error) + data.value = null + } +} + function getMenus() { return tabsRaws .filter((tab: TabItem) => (tab.groups ? tab.groups.some((group: string) => group === activePosition.value) : false)) @@ -307,7 +381,8 @@ watch(getActiveRole, () => { tabs.value = getMenus() }) -onMounted(() => { +onMounted(async () => { + await getData() tabs.value = getMenus() }) @@ -317,7 +392,10 @@ onMounted(() => {
- + +import type { PagePermission } from '~/models/role' +import Error from '~/components/pub/my-ui/error/error.vue' +import { PAGE_PERMISSIONS } from '~/lib/page-permission' + +definePageMeta({ + middleware: ['rbac'], + roles: ['doctor', 'nurse', 'admisi', 'pharmacy', 'billing', 'management'], + title: 'Tambah Kunjungan', + contentFrame: 'cf-full-width', +}) + +const route = useRoute() + +useHead({ + title: () => `${route.meta.title}`, // backtick to avoid the ts-plugin(2322) warning +}) + +const roleAccess: PagePermission = PAGE_PERMISSIONS['/outpatient/encounter'] + +const { checkRole, hasCreateAccess } = useRBAC() + +// Check if user has access to this page +const hasAccess = checkRole(roleAccess) +if (!hasAccess) { + throw createError({ + statusCode: 403, + statusMessage: 'Access denied', + }) +} + +// Define permission-based computed properties +const canCreate = hasCreateAccess(roleAccess) + + +