Merge branch 'dev' into feat/kfr-kemoterapi-174
This commit is contained in:
@@ -3,5 +3,5 @@ import EncounterHome from '~/components/content/encounter/home.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<EncounterHome classes="chemotherapy" />
|
||||
<EncounterHome display="menu" class-code="ambulatory" sub-class-code="chemo" />
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useQueryMode } from '@/composables/useQueryMode'
|
||||
|
||||
import List from './list.vue'
|
||||
import Form from './form.vue'
|
||||
|
||||
// Models
|
||||
import type { Encounter } from '~/models/encounter'
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
encounter: Encounter
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const route = useRoute()
|
||||
|
||||
const { mode, goToEntry, backToList } = useQueryCRUDMode('mode')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<List
|
||||
v-if="mode === 'list'"
|
||||
:encounter="props.encounter"
|
||||
@add="goToEntry"
|
||||
@edit="goToEntry"
|
||||
/>
|
||||
<Form
|
||||
v-else
|
||||
@back="backToList"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,74 @@
|
||||
<script setup lang="ts">
|
||||
import { z } from 'zod'
|
||||
import Entry from '~/components/app/cprj/entry.vue'
|
||||
import Action from '~/components/pub/my-ui/nav-footer/ba-dr-su.vue'
|
||||
import ActionDialog from '~/components/pub/my-ui/nav-footer/ba-su.vue'
|
||||
import Dialog from '~/components/pub/my-ui/modal/dialog.vue'
|
||||
import { CprjSoapiSchema } from '~/schemas/soapi.schema'
|
||||
import { toast } from '~/components/pub/ui/toast'
|
||||
import { handleActionSave, handleActionEdit } from '~/handlers/soapi-early.handler'
|
||||
const { backToList } = useQueryMode('mode')
|
||||
|
||||
const route = useRoute()
|
||||
const fungsional = ref([])
|
||||
const schema = CprjSoapiSchema
|
||||
const payload = ref({
|
||||
encounter_id: 0,
|
||||
time: '',
|
||||
typeCode: 'dev-record',
|
||||
value: '',
|
||||
})
|
||||
const model = ref({
|
||||
ppa: '',
|
||||
ppa_name: '',
|
||||
subjective: '',
|
||||
objective: '',
|
||||
assesment: '',
|
||||
plan: '',
|
||||
review: '',
|
||||
})
|
||||
|
||||
const isLoading = reactive<DataTableLoader>({
|
||||
isTableLoading: false,
|
||||
})
|
||||
|
||||
onMounted(() => {})
|
||||
|
||||
const cprjRef = ref()
|
||||
async function actionHandler(type: string) {
|
||||
if (type === 'back') {
|
||||
backToList()
|
||||
return
|
||||
}
|
||||
const result = await cprjRef.value?.validate()
|
||||
console.log('result', result)
|
||||
if (result?.valid) {
|
||||
console.log('data', result.data)
|
||||
handleActionSave(
|
||||
{
|
||||
...payload.value,
|
||||
value: JSON.stringify(result.data),
|
||||
encounter_id: +route.params.id,
|
||||
time: new Date().toISOString(),
|
||||
},
|
||||
{},
|
||||
toast,
|
||||
)
|
||||
} else {
|
||||
console.log('Ada error di form', result)
|
||||
}
|
||||
}
|
||||
|
||||
provide('table_data_loader', isLoading)
|
||||
</script>
|
||||
<template>
|
||||
<Entry
|
||||
ref="cprjRef"
|
||||
v-model="model"
|
||||
:schema="schema"
|
||||
type="function"
|
||||
/>
|
||||
<div class="my-2 flex justify-end py-2">
|
||||
<Action @click="actionHandler" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,183 @@
|
||||
<script setup lang="ts">
|
||||
// Components
|
||||
import Dialog from '~/components/pub/my-ui/modal/dialog.vue'
|
||||
import Header from '~/components/pub/my-ui/nav-header/prep.vue'
|
||||
import RecordConfirmation from '~/components/pub/my-ui/confirmation/record-confirmation.vue'
|
||||
import List from '~/components/app/soapi/list.vue'
|
||||
import Entry from '~/components/app/cprj/entry.vue'
|
||||
|
||||
// Helpers
|
||||
import { usePaginatedList } from '~/composables/usePaginatedList'
|
||||
import { toast } from '~/components/pub/ui/toast'
|
||||
|
||||
// Types
|
||||
import { ActionEvents, type HeaderPrep } from '~/components/pub/my-ui/data/types'
|
||||
|
||||
// Handlers
|
||||
import {
|
||||
recId,
|
||||
recAction,
|
||||
recItem,
|
||||
isReadonly,
|
||||
isProcessing,
|
||||
isFormEntryDialogOpen,
|
||||
isRecordConfirmationOpen,
|
||||
onResetState,
|
||||
handleActionSave,
|
||||
handleActionEdit,
|
||||
handleActionRemove,
|
||||
handleCancelForm,
|
||||
} from '~/handlers/consultation.handler'
|
||||
|
||||
// Services
|
||||
import { getList, getDetail } from '~/services/soapi-early.service'
|
||||
|
||||
// Models
|
||||
import type { Encounter } from '~/models/encounter'
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
encounter: Encounter
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
const emits = defineEmits(['add', 'edit'])
|
||||
const route = useRoute()
|
||||
|
||||
const { recordId } = useQueryCRUDRecordId()
|
||||
const { goToEntry, backToList } = useQueryCRUDMode('mode')
|
||||
|
||||
let units = ref<{ value: string; label: string }[]>([])
|
||||
const encounterId = ref<number>(props?.encounter?.id || 0)
|
||||
const title = ref('')
|
||||
const id = route.params.id
|
||||
|
||||
const {
|
||||
data,
|
||||
isLoading,
|
||||
paginationMeta,
|
||||
searchInput,
|
||||
handlePageChange,
|
||||
handleSearch,
|
||||
fetchData: getMyList,
|
||||
} = usePaginatedList({
|
||||
fetchFn: async ({ page, search }) => {
|
||||
const result = await getList({
|
||||
'encounter-id': id,
|
||||
typeCode: 'dev-record',
|
||||
includes: 'encounter',
|
||||
search,
|
||||
page,
|
||||
})
|
||||
if (result.success) {
|
||||
data.value = result.body.data
|
||||
}
|
||||
return { success: result.success || false, body: result.body || {} }
|
||||
},
|
||||
entityName: 'cprj',
|
||||
})
|
||||
|
||||
const headerPrep: HeaderPrep = {
|
||||
title: 'CPRJ',
|
||||
icon: 'i-lucide-box',
|
||||
refSearchNav: {
|
||||
placeholder: 'Cari (min. 3 karakter)...',
|
||||
minLength: 3,
|
||||
debounceMs: 500,
|
||||
showValidationFeedback: true,
|
||||
onInput: (value: string) => {
|
||||
searchInput.value = value
|
||||
},
|
||||
onClick: () => {},
|
||||
onClear: () => {},
|
||||
},
|
||||
addNav: {
|
||||
label: 'Tambah',
|
||||
icon: 'i-lucide-plus',
|
||||
onClick: () => {
|
||||
goToEntry()
|
||||
emits('add')
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const today = new Date()
|
||||
|
||||
provide('rec_id', recId)
|
||||
provide('rec_action', recAction)
|
||||
provide('rec_item', recItem)
|
||||
provide('table_data_loader', isLoading)
|
||||
|
||||
const getMyDetail = async (id: number | string) => {
|
||||
const result = await getDetail(id)
|
||||
if (result.success) {
|
||||
const currentValue = result.body?.data || {}
|
||||
recItem.value = currentValue
|
||||
isFormEntryDialogOpen.value = true
|
||||
}
|
||||
}
|
||||
|
||||
// Watch for row actions when recId or recAction changes
|
||||
watch([recId, recAction], () => {
|
||||
switch (recAction.value) {
|
||||
case ActionEvents.showDetail:
|
||||
getMyDetail(recId.value)
|
||||
title.value = 'Detail Konsultasi'
|
||||
isReadonly.value = true
|
||||
break
|
||||
case ActionEvents.showEdit:
|
||||
emits('edit')
|
||||
recordId.value = recId.value
|
||||
console.log('recordId', recId.value)
|
||||
break
|
||||
case ActionEvents.showConfirmDelete:
|
||||
isRecordConfirmationOpen.value = true
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
await getMyList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Header
|
||||
v-model="searchInput"
|
||||
:prep="headerPrep"
|
||||
:ref-search-nav="headerPrep.refSearchNav"
|
||||
@search="handleSearch"
|
||||
class="mb-4 xl:mb-5"
|
||||
/>
|
||||
|
||||
<List
|
||||
:data="data"
|
||||
:pagination-meta="paginationMeta"
|
||||
@page-change="handlePageChange"
|
||||
/>
|
||||
|
||||
<!-- Record Confirmation Modal -->
|
||||
<RecordConfirmation
|
||||
v-model:open="isRecordConfirmationOpen"
|
||||
action="delete"
|
||||
:record="recItem"
|
||||
@confirm="() => handleActionRemove(recId, getMyList, toast)"
|
||||
@cancel=""
|
||||
>
|
||||
<template #default="{ record }">
|
||||
<div class="text-sm">
|
||||
<p>
|
||||
<strong>ID:</strong>
|
||||
{{ record?.id }}
|
||||
</p>
|
||||
<p v-if="record?.name">
|
||||
<strong>Nama:</strong>
|
||||
{{ record.name }}
|
||||
</p>
|
||||
<p v-if="record?.code">
|
||||
<strong>Kode:</strong>
|
||||
{{ record.code }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
</RecordConfirmation>
|
||||
</template>
|
||||
@@ -20,8 +20,8 @@ const roleAccess: PagePermission = PAGE_PERMISSIONS['/rehab/encounter']
|
||||
const { getPagePermissions } = useRBAC()
|
||||
const pagePermission = getPagePermissions(roleAccess)
|
||||
|
||||
const {user,userRole} = useUserStore()
|
||||
const {getUserPermissions} = useRBAC()
|
||||
// const {user,userRole} = useUserStore()
|
||||
// const {getUserPermissions} = useRBAC()
|
||||
// #endregion
|
||||
|
||||
// #region State
|
||||
@@ -121,6 +121,7 @@ watch([recId, recAction, timestamp], () => {
|
||||
case ActionEvents.showDetail:
|
||||
isDocPreviewDialogOpen.value = true
|
||||
break
|
||||
|
||||
case ActionEvents.showEdit:
|
||||
if(pagePermission.canUpdate){
|
||||
navigateTo({
|
||||
@@ -131,22 +132,13 @@ watch([recId, recAction, timestamp], () => {
|
||||
unauthorizedToast()
|
||||
}
|
||||
break
|
||||
|
||||
case ActionEvents.showConfirmDelete:
|
||||
if(pagePermission.canDelete){
|
||||
isRecordConfirmationOpen.value = true
|
||||
} else {
|
||||
unauthorizedToast()
|
||||
}
|
||||
navigateTo(recItem.value.filePath, { external: true, open: { target: '_blank' } })
|
||||
break
|
||||
case ActionEvents.showEdit:
|
||||
navigateTo({
|
||||
name: 'rehab-encounter-id-document-upload-document_id-edit',
|
||||
params: { id: encounterId, "document_id": recId.value },
|
||||
})
|
||||
break
|
||||
case ActionEvents.showConfirmDelete:
|
||||
isRecordConfirmationOpen.value = true
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, onMounted } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
// Components
|
||||
import EncounterPatientInfo from '~/components/app/encounter/patient-info.vue'
|
||||
|
||||
// Models
|
||||
import { genEncounter } from '~/models/encounter'
|
||||
|
||||
// Handlers
|
||||
import { getEncounterData } from '~/handlers/encounter-process.handler'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const id = typeof route.params.id == 'string' ? parseInt(route.params.id) : 0
|
||||
const data = ref<any>(genEncounter())
|
||||
const isShowPatient = computed(() => data.value && data.value?.patient?.person)
|
||||
|
||||
async function getData() {
|
||||
data.value = await getEncounterData(id)
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await getData()
|
||||
})
|
||||
|
||||
function handleClick(type: string) {
|
||||
if (type === 'draft') {
|
||||
router.back()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full">
|
||||
<div class="mb-4">
|
||||
<PubMyUiNavContentBa label="Kembali ke Daftar Kunjungan" @click="handleClick" />
|
||||
</div>
|
||||
<EncounterPatientInfo v-if="isShowPatient" :data="data" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,42 +1,14 @@
|
||||
<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'
|
||||
|
||||
// Types
|
||||
import type { DataTableLoader } from '~/components/pub/my-ui/data-table/type'
|
||||
import type { TreeItem } from '~/components/pub/my-ui/select-tree/type'
|
||||
|
||||
// Constants
|
||||
import { paymentTypes, sepRefTypeCodes, participantGroups } from '~/lib/constants.vclaim'
|
||||
|
||||
// Services
|
||||
import {
|
||||
getList as getSpecialistList,
|
||||
getValueTreeItems as getSpecialistTreeItems,
|
||||
} from '~/services/specialist.service'
|
||||
import { getValueLabelList as getDoctorValueLabelList } from '~/services/doctor.service'
|
||||
import { create as createEncounter, getDetail as getEncounterDetail, update as updateEncounter } from '~/services/encounter.service'
|
||||
import { getList as getSepList } from '~/services/vclaim-sep.service'
|
||||
|
||||
// Helpers
|
||||
import { refDebounced } from '@vueuse/core'
|
||||
|
||||
// Handlers
|
||||
import {
|
||||
patients,
|
||||
selectedPatient,
|
||||
selectedPatientObject,
|
||||
paginationMeta,
|
||||
getPatientsList,
|
||||
getPatientCurrent,
|
||||
getPatientByIdentifierSearch,
|
||||
} from '~/handlers/patient.handler'
|
||||
|
||||
// Stores
|
||||
import { useUserStore } from '~/stores/user'
|
||||
import { useEncounterEntry } from '~/handlers/encounter-entry.handler'
|
||||
|
||||
const props = defineProps<{
|
||||
id: number
|
||||
@@ -46,54 +18,43 @@ const props = defineProps<{
|
||||
}>()
|
||||
|
||||
const route = useRoute()
|
||||
const userStore = useUserStore()
|
||||
const openPatient = ref(false)
|
||||
const isLoading = reactive<DataTableLoader>({
|
||||
isTableLoading: false,
|
||||
})
|
||||
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 isLoadingDetail = ref(false)
|
||||
const formRef = ref<InstanceType<typeof AppEncounterEntryForm> | null>(null)
|
||||
const encounterData = ref<any>(null)
|
||||
const formObjects = ref<any>({})
|
||||
|
||||
// SEP validation state
|
||||
const isSepValid = ref(false)
|
||||
const isCheckingSep = ref(false)
|
||||
const sepNumber = ref('')
|
||||
const {
|
||||
paymentsList,
|
||||
sepNumber,
|
||||
sepsList,
|
||||
participantGroupsList,
|
||||
specialistsTree,
|
||||
doctorsList,
|
||||
recSelectId,
|
||||
isLoadingDetail,
|
||||
formObjects,
|
||||
openPatient,
|
||||
isSepValid,
|
||||
isCheckingSep,
|
||||
isSaveDisabled,
|
||||
isSaving,
|
||||
isLoading,
|
||||
patients,
|
||||
selectedPatient,
|
||||
selectedPatientObject,
|
||||
paginationMeta,
|
||||
toNavigateSep,
|
||||
getListPath,
|
||||
handleInit,
|
||||
loadEncounterDetail,
|
||||
handleSaveEncounter,
|
||||
getPatientsList,
|
||||
getPatientCurrent,
|
||||
getPatientByIdentifierSearch,
|
||||
getIsSubspecialist,
|
||||
getValidateSepNumber,
|
||||
handleFetchDoctors,
|
||||
} = useEncounterEntry(props)
|
||||
|
||||
const debouncedSepNumber = refDebounced(sepNumber, 500)
|
||||
|
||||
// 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 || isLoadingDetail.value
|
||||
})
|
||||
|
||||
function getListPath(): 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
|
||||
}
|
||||
|
||||
function handleSavePatient() {
|
||||
selectedPatientObject.value = null
|
||||
setTimeout(() => {
|
||||
@@ -101,140 +62,15 @@ function handleSavePatient() {
|
||||
}, 150)
|
||||
}
|
||||
|
||||
function toKebabCase(str: string): string {
|
||||
return str.replace(/([A-Z])/g, '-$1').toLowerCase()
|
||||
function handleSaveClick() {
|
||||
if (formRef.value && typeof formRef.value.submitForm === 'function') {
|
||||
formRef.value.submitForm()
|
||||
}
|
||||
}
|
||||
|
||||
function toNavigateSep(values: any) {
|
||||
const queryParams = new URLSearchParams()
|
||||
if (values['subSpecialistCode']) {
|
||||
const isSub = isSubspecialist(values['subSpecialistCode'], specialistsTree.value)
|
||||
if (!isSub) {
|
||||
values['specialistCode'] = values['subSpecialistCode']
|
||||
delete values['subSpecialistCode']
|
||||
}
|
||||
}
|
||||
|
||||
Object.keys(values).forEach((field) => {
|
||||
if (values[field]) {
|
||||
queryParams.append(toKebabCase(field), values[field])
|
||||
}
|
||||
})
|
||||
|
||||
navigateTo('/integration/bpjs/sep/add' + `?${queryParams.toString()}`)
|
||||
}
|
||||
|
||||
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: null,
|
||||
unit_id: null,
|
||||
appointment_doctor_id: Number(formValues.doctorId),
|
||||
responsible_doctor_id: Number(formValues.doctorId),
|
||||
paymentType: formValues.paymentType,
|
||||
cardNumber: formValues.cardNumber,
|
||||
refSource_name: '',
|
||||
appointment_id: null,
|
||||
}
|
||||
|
||||
if (employeeId && employeeId > 0) {
|
||||
payload.adm_employee_id = employeeId
|
||||
}
|
||||
|
||||
// Add specialist_id and subspecialist_id if available
|
||||
if (specialist_id) {
|
||||
payload.specialist_id = specialist_id
|
||||
}
|
||||
if (subspecialist_id) {
|
||||
payload.subspecialist_id = subspecialist_id
|
||||
}
|
||||
|
||||
let paymentMethod = 'cash'
|
||||
if (formValues.paymentType === 'jkn' || formValues.paymentType === 'jkmm') {
|
||||
paymentMethod = 'insurance'
|
||||
} else if (formValues.paymentType === 'spm') {
|
||||
paymentMethod = 'cash'
|
||||
} else if (formValues.paymentType === 'pks') {
|
||||
paymentMethod = 'membership'
|
||||
}
|
||||
|
||||
if (paymentMethod === 'insurance') {
|
||||
payload.paymentMethod_code = paymentMethod
|
||||
payload.insuranceCompany_id = null
|
||||
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 - 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: isEditMode.value ? 'Kunjungan berhasil diperbarui' : 'Kunjungan berhasil dibuat',
|
||||
variant: 'default',
|
||||
})
|
||||
// Redirect to list page
|
||||
await navigateTo(getListPath())
|
||||
} else {
|
||||
const errorMessage = result.body?.message || (isEditMode.value ? 'Gagal memperbarui kunjungan' : '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 || (isEditMode.value ? 'Gagal memperbarui kunjungan' : 'Gagal membuat kunjungan'),
|
||||
variant: 'destructive',
|
||||
})
|
||||
} finally {
|
||||
isSaving.value = false
|
||||
function handleFetch(value?: any) {
|
||||
if (value?.subSpecialistId) {
|
||||
handleFetchDoctors(value.subSpecialistId)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,11 +82,9 @@ async function handleEvent(menu: string, value?: any) {
|
||||
} else if (menu === 'add') {
|
||||
navigateTo('/client/patient/add')
|
||||
} else if (menu === 'add-sep') {
|
||||
// If SEP is already valid, don't navigate
|
||||
if (isSepValid.value) {
|
||||
return
|
||||
}
|
||||
recSelectId.value = null
|
||||
toNavigateSep({
|
||||
isService: 'false',
|
||||
sourcePath: route.path,
|
||||
@@ -258,10 +92,7 @@ async function handleEvent(menu: string, value?: any) {
|
||||
...value,
|
||||
})
|
||||
} else if (menu === 'sep-number-changed') {
|
||||
// Update sepNumber when it changes in form (only if different to prevent loop)
|
||||
if (sepNumber.value !== value) {
|
||||
sepNumber.value = value || ''
|
||||
}
|
||||
await getValidateSepNumber(String(value || ''))
|
||||
} else if (menu === 'save') {
|
||||
await handleSaveEncounter(value)
|
||||
} else if (menu === 'cancel') {
|
||||
@@ -269,432 +100,26 @@ async function handleEvent(menu: string, value?: any) {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle save button click
|
||||
function handleSaveClick() {
|
||||
console.log('🔵 handleSaveClick called')
|
||||
console.log('🔵 formRef:', formRef.value)
|
||||
console.log('🔵 isSaveDisabled:', isSaveDisabled.value)
|
||||
console.log('🔵 selectedPatient:', selectedPatient.value)
|
||||
console.log('🔵 selectedPatientObject:', selectedPatientObject.value)
|
||||
console.log('🔵 isSaving:', isSaving.value)
|
||||
console.log('🔵 isLoadingDetail:', isLoadingDetail.value)
|
||||
|
||||
if (formRef.value && typeof formRef.value.submitForm === 'function') {
|
||||
console.log('🔵 Calling formRef.value.submitForm()')
|
||||
formRef.value.submitForm()
|
||||
} else {
|
||||
console.error('❌ formRef.value is null or submitForm is not a function')
|
||||
}
|
||||
}
|
||||
provide('rec_select_id', recSelectId)
|
||||
provide('table_data_loader', isLoading)
|
||||
|
||||
/**
|
||||
* Validate SEP number
|
||||
*/
|
||||
async function validateSepNumber(sepNumberValue: string) {
|
||||
// Reset validation if SEP number is empty
|
||||
if (!sepNumberValue || sepNumberValue.trim() === '') {
|
||||
isSepValid.value = false
|
||||
isCheckingSep.value = false
|
||||
return
|
||||
}
|
||||
|
||||
// Only check if payment type is JKN
|
||||
// We need to check from formObjects
|
||||
const paymentType = formObjects.value?.paymentType
|
||||
if (paymentType !== 'jkn') {
|
||||
isSepValid.value = false
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
isCheckingSep.value = true
|
||||
const result = await getSepList({ number: sepNumberValue.trim() })
|
||||
|
||||
// Check if SEP is found
|
||||
// If response is not null, SEP is found
|
||||
// If response is null with metaData code "201", SEP is not found
|
||||
if (result.success && result.body?.response !== null) {
|
||||
isSepValid.value = true
|
||||
} else {
|
||||
// SEP not found (response null with metaData code "201")
|
||||
isSepValid.value = false
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error checking SEP:', error)
|
||||
isSepValid.value = false
|
||||
} finally {
|
||||
isCheckingSep.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// Watch debounced SEP number to validate
|
||||
watch(debouncedSepNumber, async (newValue) => {
|
||||
await validateSepNumber(newValue)
|
||||
await getValidateSepNumber(newValue)
|
||||
})
|
||||
|
||||
// Watch payment type to reset SEP validation
|
||||
watch(
|
||||
() => formObjects.value?.paymentType,
|
||||
(newValue) => {
|
||||
isSepValid.value = false
|
||||
// If payment type is not JKN, clear SEP number
|
||||
if (newValue !== 'jkn') {
|
||||
sepNumber.value = ''
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
async function handleFetchSpecialists() {
|
||||
try {
|
||||
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) {
|
||||
console.error('Error fetching specialist-subspecialist tree:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to check if a value exists in the specialistsTree
|
||||
* Returns true if it's a leaf node (subspecialist), false if parent node (specialist)
|
||||
*/
|
||||
function isSubspecialist(value: string, items: TreeItem[]): boolean {
|
||||
for (const item of items) {
|
||||
if (item.value === value) {
|
||||
// If this item has children, it's not selected, so skip
|
||||
// If this is the selected item, check if it has children in the tree
|
||||
return false // This means it's a specialist, not a subspecialist
|
||||
}
|
||||
if (item.children) {
|
||||
for (const child of item.children) {
|
||||
if (child.value === value) {
|
||||
// This is a subspecialist (leaf node)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to get specialist/subspecialist code from ID
|
||||
* Returns code string or null if not found
|
||||
*/
|
||||
function getSpecialistCodeFromId(id: number | null | undefined): string | null {
|
||||
if (!id) return null
|
||||
|
||||
// First check if encounter has specialist object with code
|
||||
if (encounterData.value?.specialist?.id === id) {
|
||||
return encounterData.value.specialist.code || null
|
||||
}
|
||||
|
||||
// Search in specialistsData
|
||||
for (const specialist of specialistsData.value) {
|
||||
if (specialist.id === id) {
|
||||
return specialist.code || null
|
||||
}
|
||||
// Check subspecialists
|
||||
if (specialist.subspecialists && Array.isArray(specialist.subspecialists)) {
|
||||
for (const subspecialist of specialist.subspecialists) {
|
||||
if (subspecialist.id === id) {
|
||||
return subspecialist.code || null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to get subspecialist code from ID
|
||||
* Returns code string or null if not found
|
||||
*/
|
||||
function getSubspecialistCodeFromId(id: number | null | undefined): string | null {
|
||||
if (!id) return null
|
||||
|
||||
// First check if encounter has subspecialist object with code
|
||||
if (encounterData.value?.subspecialist?.id === id) {
|
||||
return encounterData.value.subspecialist.code || null
|
||||
}
|
||||
|
||||
// Search in specialistsData
|
||||
for (const specialist of specialistsData.value) {
|
||||
if (specialist.subspecialists && Array.isArray(specialist.subspecialists)) {
|
||||
for (const subspecialist of specialist.subspecialists) {
|
||||
if (subspecialist.id === id) {
|
||||
return subspecialist.code || null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
const filterParams: any = { 'page-size': 100, includes: 'employee-Person' }
|
||||
|
||||
if (!subSpecialistId) {
|
||||
const doctors = await getDoctorValueLabelList(filterParams)
|
||||
doctorsList.value = doctors
|
||||
return
|
||||
}
|
||||
|
||||
// Check if the selectd value is a subspecialist or specialist
|
||||
const isSub = isSubspecialist(subSpecialistId, specialistsTree.value)
|
||||
|
||||
if (isSub) {
|
||||
// If selected is subspecialist, filter by subspecialist-id
|
||||
filterParams['subspecialist-id'] = subSpecialistId
|
||||
} else {
|
||||
// If selected is specialist, filter by specialist-id
|
||||
filterParams['specialist-id'] = subSpecialistId
|
||||
}
|
||||
|
||||
const doctors = await getDoctorValueLabelList(filterParams)
|
||||
doctorsList.value = doctors
|
||||
} catch (error) {
|
||||
console.error('Error fetching doctors:', error)
|
||||
doctorsList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
function handleFetch(value?: any) {
|
||||
if (value?.subSpecialistId) {
|
||||
// handleFetchDoctors(value.subSpecialistId)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleInit() {
|
||||
selectedPatientObject.value = null
|
||||
paymentsList.value = Object.keys(paymentTypes).map((item) => ({
|
||||
value: item.toString(),
|
||||
label: paymentTypes[item],
|
||||
})) as any
|
||||
sepsList.value = Object.keys(sepRefTypeCodes).map((item) => ({
|
||||
value: item.toString(),
|
||||
label: sepRefTypeCodes[item],
|
||||
})) as any
|
||||
participantGroupsList.value = Object.keys(participantGroups).map((item) => ({
|
||||
value: item.toString(),
|
||||
label: participantGroups[item],
|
||||
})) as any
|
||||
// Fetch tree data
|
||||
await handleFetchDoctors()
|
||||
await handleFetchSpecialists()
|
||||
}
|
||||
|
||||
/**
|
||||
* Load encounter detail data for edit mode
|
||||
*/
|
||||
async function loadEncounterDetail() {
|
||||
if (!isEditMode.value || props.id <= 0) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
isLoadingDetail.value = true
|
||||
// Include patient, person, specialist, and subspecialist in the response
|
||||
const result = await getEncounterDetail(props.id, {
|
||||
includes: 'patient,patient-person,specialist,subspecialist',
|
||||
})
|
||||
if (result.success && result.body?.data) {
|
||||
encounterData.value = result.body.data
|
||||
await mapEncounterToForm(encounterData.value)
|
||||
// Set loading to false after mapping is complete
|
||||
isLoadingDetail.value = false
|
||||
} 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 - use data from response if available
|
||||
if (encounter.patient) {
|
||||
selectedPatient.value = String(encounter.patient.id)
|
||||
selectedPatientObject.value = encounter.patient
|
||||
// Only fetch patient if person data is missing
|
||||
if (!encounter.patient.person) {
|
||||
await getPatientCurrent(selectedPatient.value)
|
||||
}
|
||||
}
|
||||
|
||||
// Map form fields
|
||||
const formData: any = {}
|
||||
|
||||
// Patient data (readonly, populated from selected patient)
|
||||
// Use encounter.patient.person which is already in the response
|
||||
if (encounter.patient?.person) {
|
||||
formData.patientName = encounter.patient.person.name || ''
|
||||
formData.nationalIdentity = encounter.patient.person.residentIdentityNumber || ''
|
||||
formData.medicalRecordNumber = encounter.patient.number || ''
|
||||
} else if (selectedPatientObject.value?.person) {
|
||||
// Fallback to selectedPatientObject if encounter.patient.person is not available
|
||||
formData.patientName = selectedPatientObject.value.person.name || ''
|
||||
formData.nationalIdentity = selectedPatientObject.value.person.residentIdentityNumber || ''
|
||||
formData.medicalRecordNumber = selectedPatientObject.value.number || ''
|
||||
}
|
||||
|
||||
// Doctor ID
|
||||
const doctorId = encounter.appointment_doctor_id || encounter.responsible_doctor_id
|
||||
if (doctorId) {
|
||||
formData.doctorId = String(doctorId)
|
||||
}
|
||||
|
||||
// Specialist/Subspecialist - use helper function to get code from ID
|
||||
// Priority: subspecialist_id > specialist_id
|
||||
if (encounter.subspecialist_id) {
|
||||
const subspecialistCode = getSubspecialistCodeFromId(encounter.subspecialist_id)
|
||||
if (subspecialistCode) {
|
||||
formData.subSpecialistId = subspecialistCode
|
||||
}
|
||||
} else if (encounter.specialist_id) {
|
||||
const specialistCode = getSpecialistCodeFromId(encounter.specialist_id)
|
||||
if (specialistCode) {
|
||||
formData.subSpecialistId = specialistCode
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: if encounter has specialist/subspecialist object with code
|
||||
if (!formData.subSpecialistId) {
|
||||
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 - use fields directly from encounter
|
||||
// Map paymentMethod_code to paymentType
|
||||
if (encounter.paymentMethod_code) {
|
||||
// Map paymentMethod_code to paymentType
|
||||
// 'insurance' typically means JKN/JKMM
|
||||
if (encounter.paymentMethod_code === 'insurance') {
|
||||
formData.paymentType = 'jkn' // Default to JKN for insurance
|
||||
} else {
|
||||
// For other payment methods, use the code directly if it matches
|
||||
// Otherwise default to 'spm'
|
||||
const validPaymentTypes = ['jkn', 'jkmm', 'spm', 'pks']
|
||||
if (validPaymentTypes.includes(encounter.paymentMethod_code)) {
|
||||
formData.paymentType = encounter.paymentMethod_code
|
||||
} else {
|
||||
formData.paymentType = 'spm' // Default to SPM
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If paymentMethod_code is empty or null, default to 'spm'
|
||||
formData.paymentType = 'spm'
|
||||
}
|
||||
|
||||
// Map payment fields directly from encounter
|
||||
formData.cardNumber = encounter.member_number || ''
|
||||
formData.sepNumber = encounter.ref_number || ''
|
||||
|
||||
// Note: patientCategory and sepType might not be available in the response
|
||||
// These fields might need to be set manually or fetched from other sources
|
||||
|
||||
// Set form objects for the form component
|
||||
formObjects.value = formData
|
||||
|
||||
// Update sepNumber for validation
|
||||
if (formData.sepNumber) {
|
||||
sepNumber.value = formData.sepNumber
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if (props.id > 0) {
|
||||
await loadEncounterDetail()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,190 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
import { getDetail } from '~/services/encounter.service'
|
||||
|
||||
import type { TabItem } from '~/components/pub/my-ui/comp-tab/type'
|
||||
import CompTab from '~/components/pub/my-ui/comp-tab/comp-tab.vue'
|
||||
|
||||
// PLASE ORDER BY TAB POSITION
|
||||
import Status from '~/components/content/encounter/status.vue'
|
||||
import AssesmentFunctionList from '~/components/content/assesment-function/list.vue'
|
||||
import EarlyMedicalAssesmentList from '~/components/content/soapi/entry.vue'
|
||||
import EarlyMedicalRehabList from '~/components/content/soapi/entry.vue'
|
||||
import PrescriptionList from '~/components/content/prescription/list.vue'
|
||||
import Consultation from '~/components/content/consultation/list.vue'
|
||||
import ProtocolList from '~/components/app/chemotherapy/list.protocol.vue'
|
||||
import MedicineProtocolList from '~/components/app/chemotherapy/list.medicine.vue'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const props = defineProps<{
|
||||
classes?: string
|
||||
}>()
|
||||
|
||||
// activeTab selalu sinkron dengan query param
|
||||
const activeTab = computed({
|
||||
get: () => (route.query?.tab && typeof route.query.tab === 'string' ? route.query.tab : 'status'),
|
||||
set: (val: string) => {
|
||||
router.replace({ path: route.path, query: { tab: val } })
|
||||
},
|
||||
})
|
||||
|
||||
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<any>({
|
||||
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: '',
|
||||
},
|
||||
},
|
||||
visitDate: new Date().toISOString(),
|
||||
unit: { name: 'Onkologi' },
|
||||
responsible_doctor: null,
|
||||
appointment_doctor: { employee: { person: { name: 'Dr. Clara Smith', frontTitle: 'Dr.', endTitle: 'Sp.OG' } } },
|
||||
})
|
||||
|
||||
// Dummy rows for ProtocolList (matches keys expected by list-cfg.protocol)
|
||||
const protocolRows = [
|
||||
{
|
||||
number: '1',
|
||||
tanggal: new Date().toISOString().substring(0, 10),
|
||||
siklus: 'I',
|
||||
periode: 'Siklus I',
|
||||
kehadiran: 'Hadir',
|
||||
action: '',
|
||||
},
|
||||
{
|
||||
number: '2',
|
||||
tanggal: new Date().toISOString().substring(0, 10),
|
||||
siklus: 'II',
|
||||
periode: 'Siklus II',
|
||||
kehadiran: 'Tidak Hadir',
|
||||
action: '',
|
||||
},
|
||||
]
|
||||
|
||||
const paginationMeta = {
|
||||
recordCount: protocolRows.length,
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
totalPage: 1,
|
||||
hasNext: false,
|
||||
hasPrev: false,
|
||||
}
|
||||
|
||||
const tabsRaws: TabItem[] = [
|
||||
{
|
||||
value: 'status',
|
||||
label: 'Status Masuk/Keluar',
|
||||
groups: ['ambulatory', 'rehabilitation', 'chemotherapy'],
|
||||
component: Status,
|
||||
props: { encounter: data },
|
||||
},
|
||||
{
|
||||
value: 'early-medical-assessment',
|
||||
label: 'Pengkajian Awal Medis',
|
||||
groups: ['ambulatory', 'rehabilitation', 'chemotherapy'],
|
||||
component: EarlyMedicalAssesmentList,
|
||||
},
|
||||
{
|
||||
value: 'rehab-medical-assessment',
|
||||
label: 'Pengkajian Awal Medis Rehabilitasi Medis',
|
||||
groups: ['ambulatory', 'rehabilitation', 'chemotherapy'],
|
||||
component: EarlyMedicalRehabList,
|
||||
},
|
||||
{
|
||||
value: 'function-assessment',
|
||||
label: 'Asesmen Fungsi',
|
||||
groups: ['ambulatory', 'rehabilitation'],
|
||||
component: AssesmentFunctionList,
|
||||
},
|
||||
{ value: 'therapy-protocol', groups: ['ambulatory', 'rehabilitation'], label: 'Protokol Terapi' },
|
||||
{
|
||||
value: 'chemotherapy-protocol',
|
||||
label: 'Protokol Kemoterapi',
|
||||
groups: ['chemotherapy'],
|
||||
component: ProtocolList,
|
||||
props: { data: protocolRows, paginationMeta },
|
||||
},
|
||||
{
|
||||
value: 'chemotherapy-medicine',
|
||||
label: 'Protokol Obat Kemoterapi',
|
||||
groups: ['chemotherapy'],
|
||||
component: MedicineProtocolList,
|
||||
props: { data: protocolRows, paginationMeta },
|
||||
},
|
||||
{ value: 'report', label: 'Laporan Tindakan', groups: ['chemotherapy'] },
|
||||
{ value: 'patient-note', label: 'CPRJ', groups: ['ambulatory', 'rehabilitation', 'chemotherapy'] },
|
||||
{
|
||||
value: 'education-assessment',
|
||||
label: 'Asesmen Kebutuhan Edukasi',
|
||||
groups: ['ambulatory', 'rehabilitation', 'chemotherapy'],
|
||||
},
|
||||
{ value: 'consent', label: 'General Consent', groups: ['ambulatory', 'rehabilitation', 'chemotherapy'] },
|
||||
{ value: 'patient-note', label: 'CPRJ', groups: ['ambulatory', 'rehabilitation', 'chemotherapy'] },
|
||||
{
|
||||
value: 'prescription',
|
||||
label: 'Order Obat',
|
||||
groups: ['ambulatory', 'rehabilitation', 'chemotherapy'],
|
||||
component: PrescriptionList,
|
||||
},
|
||||
{ value: 'device', label: 'Order Alkes', groups: ['ambulatory', 'rehabilitation', 'chemotherapy'] },
|
||||
{ value: 'mcu-radiology', label: 'Order Radiologi', groups: ['ambulatory', 'rehabilitation', 'chemotherapy'] },
|
||||
{ value: 'mcu-lab-pc', label: 'Order Lab PK', groups: ['ambulatory', 'rehabilitation', 'chemotherapy'] },
|
||||
{ value: 'mcu-lab-micro', label: 'Order Lab Mikro', groups: ['ambulatory', 'rehabilitation', 'chemotherapy'] },
|
||||
{ value: 'mcu-lab-pa', label: 'Order Lab PA', groups: ['ambulatory', 'rehabilitation', 'chemotherapy'] },
|
||||
{ value: 'medical-action', label: 'Order Ruang Tindakan', groups: ['ambulatory', 'rehabilitation', 'chemotherapy'] },
|
||||
{ value: 'mcu-result', label: 'Hasil Penunjang', groups: ['ambulatory', 'rehabilitation', 'chemotherapy'] },
|
||||
{
|
||||
value: 'consultation',
|
||||
label: 'Konsultasi',
|
||||
groups: ['ambulatory', 'rehabilitation', 'chemotherapy'],
|
||||
component: Consultation,
|
||||
props: { encounter: data },
|
||||
},
|
||||
{ value: 'resume', label: 'Resume', groups: ['ambulatory', 'rehabilitation', 'chemotherapy'] },
|
||||
{ value: 'control', label: 'Surat Kontrol', groups: ['ambulatory', 'rehabilitation', 'chemotherapy'] },
|
||||
{ value: 'screening', label: 'Skrinning MPP', groups: ['ambulatory', 'rehabilitation', 'chemotherapy'] },
|
||||
{ value: 'supporting-document', label: 'Upload Dokumen Pendukung', groups: ['ambulatory', 'rehabilitation'] },
|
||||
{ value: 'price-list', label: 'Tarif Tindakan', groups: ['ambulatory', 'rehabilitation', 'chemotherapy'] },
|
||||
]
|
||||
|
||||
const tabs = computed(() => {
|
||||
return tabsRaws
|
||||
.filter((tab: TabItem) => tab.groups ? tab.groups.some((group: string) => props.classes?.includes(group)) : true)
|
||||
.map((tab: TabItem) => {
|
||||
return { ...tab, props: { ...tab.props, encounter: data } }
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full">
|
||||
<div class="mb-4">
|
||||
<PubMyUiNavContentBa label="Kembali ke Daftar Kunjungan" />
|
||||
</div>
|
||||
<AppEncounterQuickInfo :data="data" />
|
||||
<CompTab
|
||||
:data="tabs"
|
||||
:initial-active-tab="activeTab"
|
||||
@change-tab="activeTab = $event"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,30 +1,40 @@
|
||||
<script setup lang="ts">
|
||||
// Components
|
||||
import { Calendar, Hospital, UserCheck, UsersRound } from 'lucide-vue-next'
|
||||
import SummaryCard from '~/components/pub/my-ui/summary-card/summary-card.vue'
|
||||
import Dialog from '~/components/pub/my-ui/modal/dialog.vue'
|
||||
import Header from '~/components/pub/my-ui/nav-header/prep.vue'
|
||||
import Filter from '~/components/pub/my-ui/nav-header/filter.vue'
|
||||
import RecordConfirmation from '~/components/pub/my-ui/confirmation/record-confirmation.vue'
|
||||
|
||||
// Types
|
||||
///// Imports
|
||||
// Pub components
|
||||
import type { DataTableLoader } from '~/components/pub/my-ui/data-table/type'
|
||||
import type { Summary } from '~/components/pub/my-ui/summary-card/type'
|
||||
import type { HeaderPrep, RefSearchNav } from '~/components/pub/my-ui/data/types'
|
||||
import { toast } from '~/components/pub/ui/toast'
|
||||
import { ActionEvents } from '~/components/pub/my-ui/data/types'
|
||||
import Dialog from '~/components/pub/my-ui/modal/dialog.vue'
|
||||
import * as CH from '~/components/pub/my-ui/content-header'
|
||||
import RecordConfirmation from '~/components/pub/my-ui/confirmation/record-confirmation.vue'
|
||||
import { useSidebar } from '~/components/pub/ui/sidebar/utils'
|
||||
|
||||
// App libs
|
||||
import { getServicePosition } from '~/lib/roles' // previously getPositionAs
|
||||
|
||||
// Services
|
||||
import { getList as getEncounterList, remove as removeEncounter } from '~/services/encounter.service'
|
||||
import { getList as getEncounterList, remove as removeEncounter, cancel as cancelEncounter } from '~/services/encounter.service'
|
||||
|
||||
// UI
|
||||
import { toast } from '~/components/pub/ui/toast'
|
||||
// Apps
|
||||
import Content from '~/components/app/encounter/list.vue'
|
||||
import FilterNav from '~/components/app/encounter/filter-nav.vue'
|
||||
import FilterForm from '~/components/app/encounter/filter-form.vue'
|
||||
|
||||
// Props
|
||||
const props = defineProps<{
|
||||
classCode?: 'ambulatory' | 'emergency' | 'inpatient' | 'outpatient'
|
||||
classCode?: 'ambulatory' | 'emergency' | 'inpatient'
|
||||
subClassCode?: 'reg' | 'rehab' | 'chemo' | 'emg' | 'eon' | 'op' | 'icu' | 'hcu' | 'vk'
|
||||
type: string
|
||||
canCreate?: boolean
|
||||
canUpdate?: boolean
|
||||
canDelete?: boolean
|
||||
}>()
|
||||
|
||||
///// Declarations and Flows
|
||||
// Sidebar automation
|
||||
const { setOpen } = useSidebar()
|
||||
setOpen(true)
|
||||
|
||||
// Main data
|
||||
const data = ref([])
|
||||
const isLoading = reactive<DataTableLoader>({
|
||||
summary: false,
|
||||
@@ -33,66 +43,88 @@ const isLoading = reactive<DataTableLoader>({
|
||||
const recId = ref<number>(0)
|
||||
const recAction = ref<string>('')
|
||||
const recItem = ref<any>(null)
|
||||
const isFormEntryDialogOpen = ref(false)
|
||||
const isFilterFormDialogOpen = ref(false)
|
||||
const isRecordConfirmationOpen = ref(false)
|
||||
const isRecordCancelOpen = ref(false)
|
||||
|
||||
const hreaderPrep: HeaderPrep = {
|
||||
// Headers
|
||||
const hreaderPrep: CH.Config = {
|
||||
title: 'Kunjungan',
|
||||
icon: 'i-lucide-users',
|
||||
addNav: {
|
||||
label: 'Tambah',
|
||||
onClick: () => {
|
||||
if (props.classCode === 'ambulatory' && props.subClassCode === 'rehab') {
|
||||
navigateTo('/rehab/encounter/add')
|
||||
}
|
||||
if (props.classCode === 'ambulatory' && props.subClassCode === 'reg') {
|
||||
navigateTo('/outpatient/encounter/add')
|
||||
}
|
||||
if (props.classCode === 'emergency') {
|
||||
navigateTo('/emergency/encounter/add')
|
||||
}
|
||||
if (props.classCode === 'inpatient') {
|
||||
navigateTo('/inpatient/encounter/add')
|
||||
}
|
||||
navigateTo(`/${props.classCode}/encounter/add`)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const refSearchNav: RefSearchNav = {
|
||||
onClick: () => {
|
||||
// open filter modal
|
||||
isFormEntryDialogOpen.value = true
|
||||
console.log(' 1open filter modal')
|
||||
},
|
||||
onInput: (_val: string) => {
|
||||
// filter patient list
|
||||
},
|
||||
onClear: () => {
|
||||
// clear url param
|
||||
},
|
||||
if (!props.canCreate) {
|
||||
delete hreaderPrep.addNav
|
||||
}
|
||||
|
||||
// Loading state management
|
||||
// Filters
|
||||
const filter = ref<{
|
||||
installation: {
|
||||
msg: {
|
||||
placeholder: string
|
||||
}
|
||||
items: {
|
||||
value: string
|
||||
label: string
|
||||
code: string
|
||||
}[]
|
||||
}
|
||||
schema: any
|
||||
initialValues?: Partial<any>
|
||||
errors?: any
|
||||
}>({
|
||||
installation: {
|
||||
msg: {
|
||||
placeholder: 'Pilih',
|
||||
},
|
||||
items: [],
|
||||
},
|
||||
schema: {},
|
||||
})
|
||||
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
// Role reactivities
|
||||
const { getActiveRole } = useUserStore()
|
||||
const activeServicePosition = ref(getServicePosition(getActiveRole()))
|
||||
provide('activeServicePosition', activeServicePosition)
|
||||
watch(getActiveRole, (role? : string) => {
|
||||
activeServicePosition.value = getServicePosition(role)
|
||||
})
|
||||
|
||||
// Recrod reactivities
|
||||
provide('rec_id', recId)
|
||||
provide('rec_action', recAction)
|
||||
provide('rec_item', recItem)
|
||||
provide('table_data_loader', isLoading)
|
||||
watch(() => recAction.value, () => {
|
||||
const basePath = `/${props.classCode}/encounter`
|
||||
// console.log(`${basePath}/${recId.value}`, recAction.value)
|
||||
// return
|
||||
if (recAction.value === ActionEvents.showConfirmDelete) {
|
||||
isRecordConfirmationOpen.value = true
|
||||
} else if (recAction.value === ActionEvents.showCancel) {
|
||||
isRecordCancelOpen.value = true
|
||||
} else if (recAction.value === ActionEvents.showDetail) {
|
||||
navigateTo(`${basePath}/${recId.value}`)
|
||||
} else if (recAction.value === ActionEvents.showEdit) {
|
||||
navigateTo(`${basePath}/${recId.value}/edit`)
|
||||
} else if (recAction.value === ActionEvents.showProcess) {
|
||||
navigateTo(`${basePath}/${recId.value}/process`)
|
||||
} else if (recAction.value === ActionEvents.showConfirmDelete) {
|
||||
isRecordConfirmationOpen.value = true
|
||||
}
|
||||
recAction.value = '' // reset
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
getPatientList()
|
||||
})
|
||||
|
||||
/////// Functions
|
||||
async function getPatientList() {
|
||||
isLoading.isTableLoading = true
|
||||
try {
|
||||
@@ -114,6 +146,43 @@ async function getPatientList() {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle confirmation result
|
||||
async function handleConfirmCancel(record: any, action: string) {
|
||||
if (action === 'deactivate' && record?.id) {
|
||||
try {
|
||||
const result = await cancelEncounter(record.id)
|
||||
if (result.success) {
|
||||
toast({
|
||||
title: 'Berhasil',
|
||||
description: 'Kunjungan berhasil dibatalkan',
|
||||
variant: 'default',
|
||||
})
|
||||
await getPatientList() // Refresh list
|
||||
} else {
|
||||
const errorMessage = result.body?.message || 'Gagal membatalkan kunjungan'
|
||||
toast({
|
||||
title: 'Gagal',
|
||||
description: errorMessage,
|
||||
variant: 'destructive',
|
||||
})
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('Error cancellation encounter:', error)
|
||||
toast({
|
||||
title: 'Gagal',
|
||||
description: error?.message || 'Gagal membatalkan kunjungan',
|
||||
variant: 'destructive',
|
||||
})
|
||||
} finally {
|
||||
// Reset state
|
||||
recId.value = 0
|
||||
recAction.value = ''
|
||||
recItem.value = null
|
||||
isRecordCancelOpen.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle confirmation result
|
||||
async function handleConfirmDelete(record: any, action: string) {
|
||||
if (action === 'delete' && record?.id) {
|
||||
@@ -156,90 +225,72 @@ function handleCancelConfirmation() {
|
||||
recId.value = 0
|
||||
recAction.value = ''
|
||||
recItem.value = null
|
||||
isRecordConfirmationOpen.value = false
|
||||
isRecordCancelOpen.value = false
|
||||
}
|
||||
|
||||
watch(
|
||||
() => recAction.value,
|
||||
() => {
|
||||
if (recAction.value === ActionEvents.showConfirmDelete) {
|
||||
isRecordConfirmationOpen.value = true
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
provide('rec_id', recId)
|
||||
provide('rec_action', recAction)
|
||||
provide('rec_item', recItem)
|
||||
provide('table_data_loader', isLoading)
|
||||
|
||||
onMounted(() => {
|
||||
getPatientList()
|
||||
})
|
||||
function handleRemoveConfirmation() {
|
||||
// Reset record state when cancelled
|
||||
recId.value = 0
|
||||
recAction.value = ''
|
||||
recItem.value = null
|
||||
isRecordConfirmationOpen.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Header
|
||||
:prep="{ ...hreaderPrep }"
|
||||
:ref-search-nav="refSearchNav"
|
||||
/>
|
||||
<Separator class="my-4 xl:my-5" />
|
||||
<CH.ContentHeader v-bind="hreaderPrep">
|
||||
<FilterNav
|
||||
@onFilterClick="() => isFilterFormDialogOpen = true"
|
||||
@onExportPdf="() => {}"
|
||||
@onExportExcel="() => {}"
|
||||
@nExportCsv="() => {}"
|
||||
/>
|
||||
</CH.ContentHeader>
|
||||
|
||||
<Filter
|
||||
:prep="hreaderPrep"
|
||||
:ref-search-nav="refSearchNav"
|
||||
/>
|
||||
|
||||
<AppEncounterList :data="data" />
|
||||
<Content :data="data" />
|
||||
|
||||
<!-- Filter -->
|
||||
<Dialog
|
||||
v-model:open="isFormEntryDialogOpen"
|
||||
v-model:open="isFilterFormDialogOpen"
|
||||
title="Filter"
|
||||
size="lg"
|
||||
prevent-outside
|
||||
>
|
||||
<AppEncounterFilter
|
||||
:installation="{
|
||||
msg: { placeholder: 'Pilih' },
|
||||
items: [],
|
||||
}"
|
||||
:schema="{}"
|
||||
/>
|
||||
<FilterForm v-bind="filter" />
|
||||
</Dialog>
|
||||
|
||||
<!-- Record Confirmation Modal -->
|
||||
<!-- Batal -->
|
||||
<RecordConfirmation
|
||||
v-model:open="isRecordCancelOpen"
|
||||
custom-title="Batalkan Kunjungan"
|
||||
custom-message="Apakah anda yakin ingin membatalkan kunjungan pasien berikut?"
|
||||
action="deactivate"
|
||||
:record="recItem"
|
||||
@confirm="handleConfirmCancel"
|
||||
@cancel="handleCancelConfirmation"
|
||||
>
|
||||
<template #default="{ record }">
|
||||
<div class="text-sm">
|
||||
<p v-if="record?.patient?.person?.name">
|
||||
<strong>Nama:</strong>
|
||||
{{ record.patient.person.name }}
|
||||
</p>
|
||||
<p v-if="record?.medical_record_number">
|
||||
<strong>No RM:</strong>
|
||||
{{ record.medical_record_number }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
</RecordConfirmation>
|
||||
|
||||
<!-- Hapus -->
|
||||
<RecordConfirmation
|
||||
v-if="canDelete"
|
||||
v-model:open="isRecordConfirmationOpen"
|
||||
action="delete"
|
||||
:record="recItem"
|
||||
@confirm="handleConfirmDelete"
|
||||
@cancel="handleCancelConfirmation"
|
||||
@cancel="handleRemoveConfirmation"
|
||||
>
|
||||
<template #default="{ record }">
|
||||
<div class="text-sm">
|
||||
@@ -258,4 +309,11 @@ onMounted(() => {
|
||||
</div>
|
||||
</template>
|
||||
</RecordConfirmation>
|
||||
<Dialog
|
||||
title="Hapus data"
|
||||
size="md"
|
||||
v-model:open="isRecordConfirmationOpen"
|
||||
>
|
||||
Hak akses tidak memenuhi kriteria untuk proses ini.
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
<script setup lang="ts">
|
||||
//
|
||||
import { computed } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
import { getDetail } from '~/services/encounter.service'
|
||||
|
||||
//
|
||||
import type { TabItem } from '~/components/pub/my-ui/comp-tab/type'
|
||||
import CompTab from '~/components/pub/my-ui/comp-tab/comp-tab.vue'
|
||||
|
||||
import { genEncounter } from '~/models/encounter'
|
||||
|
||||
// PLASE ORDER BY TAB POSITION
|
||||
import Status from '~/components/content/encounter/status.vue'
|
||||
import AssesmentFunctionList from '~/components/content/soapi/entry.vue'
|
||||
import EarlyMedicalAssesmentList from '~/components/content/soapi/entry.vue'
|
||||
import EarlyMedicalRehabList from '~/components/content/soapi/entry.vue'
|
||||
import DeviceOrder from '~/components/content/device-order/main.vue'
|
||||
import Prescription from '~/components/content/prescription/main.vue'
|
||||
import CpLabOrder from '~/components/content/cp-lab-order/main.vue'
|
||||
import Radiology from '~/components/content/radiology-order/main.vue'
|
||||
import Consultation from '~/components/content/consultation/list.vue'
|
||||
import Cprj from '~/components/content/cprj/entry.vue'
|
||||
import DocUploadList from '~/components/content/document-upload/list.vue'
|
||||
import GeneralConsentList from '~/components/content/general-consent/entry.vue'
|
||||
import ResumeList from '~/components/content/resume/list.vue'
|
||||
import ControlLetterList from '~/components/content/control-letter/list.vue'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
// activeTab selalu sinkron dengan query param
|
||||
const activeTab = computed({
|
||||
get: () => (route.query?.tab && typeof route.query.tab === 'string' ? route.query.tab : 'status'),
|
||||
set: (val: string) => {
|
||||
router.replace({ path: route.path, query: { tab: val } })
|
||||
},
|
||||
})
|
||||
|
||||
const id = typeof route.params.id == 'string' ? parseInt(route.params.id) : 0
|
||||
const data = ref(genEncounter())
|
||||
|
||||
async function fetchDetail() {
|
||||
const res = await getDetail(id, {
|
||||
includes:
|
||||
'patient,patient-person,patient-person-addresses,unit,Appointment_Doctor,Appointment_Doctor-employee,Appointment_Doctor-employee-person,EncounterDocuments',
|
||||
})
|
||||
if (res.body?.data) data.value = res.body?.data
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchDetail()
|
||||
})
|
||||
|
||||
const tabs: TabItem[] = [
|
||||
{ value: 'status', label: 'Status Masuk/Keluar', component: Status, props: { encounter: data } },
|
||||
{
|
||||
value: 'early-medical-assessment',
|
||||
label: 'Pengkajian Awal Medis',
|
||||
component: EarlyMedicalAssesmentList,
|
||||
props: { encounter: data, type: 'early-medic', label: 'Pengkajian Awal Medis' },
|
||||
},
|
||||
{
|
||||
value: 'rehab-medical-assessment',
|
||||
label: 'Pengkajian Awal Medis Rehabilitasi Medis',
|
||||
component: EarlyMedicalRehabList,
|
||||
props: { encounter: data, type: 'early-rehab', label: 'Pengkajian Awal Medis Rehabilitasi Medis' },
|
||||
},
|
||||
{
|
||||
value: 'function-assessment',
|
||||
label: 'Asesmen Fungsi',
|
||||
component: AssesmentFunctionList,
|
||||
props: { encounter: data, type: 'function', label: 'Asesmen Fungsi' },
|
||||
},
|
||||
{ value: 'therapy-protocol', label: 'Protokol Terapi' },
|
||||
{ value: 'education-assessment', label: 'Asesmen Kebutuhan Edukasi' },
|
||||
{ value: 'patient-note', label: 'CPRJ', component: Cprj, props: { encounter: data } },
|
||||
{ value: 'consent', label: 'General Consent', component: GeneralConsentList, props: { encounter: data } },
|
||||
{ value: 'prescription', label: 'Order Obat', component: Prescription, props: { encounter_id: data.value.id } },
|
||||
{ value: 'device-order', label: 'Order Alkes', component: DeviceOrder, props: { encounter_id: data.value.id } },
|
||||
{ value: 'device', label: 'Order Alkes' },
|
||||
{ value: 'mcu-radiology', label: 'Order Radiologi', component: Radiology, props: { encounter_id: data.id } },
|
||||
{ value: 'mcu-lab-cp', label: 'Order Lab PK', component: CpLabOrder, props: { encounter_id: data.id } },
|
||||
{ value: 'mcu-lab-micro', label: 'Order Lab Mikro' },
|
||||
{ value: 'mcu-lab-pa', label: 'Order Lab PA' },
|
||||
{ value: 'medical-action', label: 'Order Ruang Tindakan' },
|
||||
{ value: 'mcu-result', label: 'Hasil Penunjang' },
|
||||
{ value: 'consultation', label: 'Konsultasi', component: Consultation, props: { encounter: data } },
|
||||
{ value: 'resume', label: 'Resume', component: ResumeList, props: { encounter: data } },
|
||||
{ value: 'control', label: 'Surat Kontrol', component: ControlLetterList, props: { encounter: data } },
|
||||
{ value: 'screening', label: 'Skrinning MPP' },
|
||||
{
|
||||
value: 'supporting-document',
|
||||
label: 'Upload Dokumen Pendukung',
|
||||
component: DocUploadList,
|
||||
props: { encounter: data },
|
||||
},
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full">
|
||||
<div class="mb-4">
|
||||
<PubMyUiNavContentBa label="Kembali ke Daftar Kunjungan" />
|
||||
</div>
|
||||
<AppEncounterQuickInfo :data="data" />
|
||||
<CompTab
|
||||
:data="tabs"
|
||||
:initial-active-tab="activeTab"
|
||||
@change-tab="activeTab = $event"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,110 +1,142 @@
|
||||
<script setup lang="ts">
|
||||
//
|
||||
import { computed } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
///// Imports
|
||||
// Pubs components
|
||||
import ContentSwitcher from '~/components/pub/my-ui/content-switcher/content-switcher.vue'
|
||||
import { useSidebar } from '~/components/pub/ui/sidebar/utils'
|
||||
import SubMenu from '~/components/pub/my-ui/menus/submenu.vue'
|
||||
import ContentNavBa from '~/components/pub/my-ui/nav-content/ba.vue'
|
||||
|
||||
import { getDetail } from '~/services/encounter.service'
|
||||
// App libs
|
||||
import { getServicePosition } from '~/lib/roles' // previously getPositionAs
|
||||
|
||||
//
|
||||
import type { TabItem } from '~/components/pub/my-ui/comp-tab/type'
|
||||
import CompTab from '~/components/pub/my-ui/comp-tab/comp-tab.vue'
|
||||
// App Models
|
||||
import { genEncounter, type Encounter } from '~/models/encounter'
|
||||
|
||||
import { genEncounter } from '~/models/encounter'
|
||||
// Handlers
|
||||
import type { EncounterProps } from '~/handlers/encounter-init.handler'
|
||||
import { getEncounterData } from '~/handlers/encounter-process.handler'
|
||||
import { getMenuItems } from "~/handlers/encounter-init.handler"
|
||||
|
||||
// PLASE ORDER BY TAB POSITION
|
||||
import Status from '~/components/content/encounter/status.vue'
|
||||
import AssesmentFunctionList from '~/components/content/soapi/entry.vue'
|
||||
import EarlyMedicalAssesmentList from '~/components/content/soapi/entry.vue'
|
||||
import EarlyMedicalRehabList from '~/components/content/soapi/entry.vue'
|
||||
import KfrList from '~/components/content/kfr/list.vue'
|
||||
import TherapyProtocolList from '~/components/content/therapy-protocol/list.vue'
|
||||
import DeviceOrder from '~/components/content/device-order/main.vue'
|
||||
import Prescription from '~/components/content/prescription/main.vue'
|
||||
import CpLabOrder from '~/components/content/cp-lab-order/main.vue'
|
||||
import Radiology from '~/components/content/radiology-order/main.vue'
|
||||
import Consultation from '~/components/content/consultation/list.vue'
|
||||
import DocUploadList from '~/components/content/document-upload/list.vue'
|
||||
import GeneralConsentList from '~/components/content/general-consent/entry.vue'
|
||||
import ResumeList from '~/components/content/resume/list.vue'
|
||||
import ControlLetterList from '~/components/content/control-letter/list.vue'
|
||||
// App Components
|
||||
import EncounterPatientInfo from '~/components/app/encounter/quick-info.vue'
|
||||
import EncounterHistoryButtonMenu from '~/components/app/encounter/quick-shortcut.vue'
|
||||
|
||||
///// Declarations and Flows
|
||||
// Props
|
||||
const props = defineProps<{
|
||||
classCode: EncounterProps['classCode']
|
||||
subClassCode?: EncounterProps['subClassCode']
|
||||
}>()
|
||||
|
||||
// Common preparations
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
// activeTab selalu sinkron dengan query param
|
||||
const activeTab = computed({
|
||||
get: () => (route.query?.tab && typeof route.query.tab === 'string' ? route.query.tab : 'status'),
|
||||
set: (val: string) => {
|
||||
router.replace({ path: route.path, query: { tab: val } })
|
||||
const { user, userActiveRole, getActiveRole } = useUserStore()
|
||||
const activeRole = getActiveRole()
|
||||
const activePosition = ref(getServicePosition(activeRole))
|
||||
const menus = ref([] as any)
|
||||
const activeMenu = computed({
|
||||
get: () => (route.query?.menu && typeof route.query.menu === 'string' ? route.query.menu : 'status'),
|
||||
set: (value: string) => {
|
||||
router.replace({ path: route.path, query: { menu: value } })
|
||||
},
|
||||
})
|
||||
|
||||
const id = typeof route.params.id == 'string' ? parseInt(route.params.id) : 0
|
||||
const data = ref(genEncounter())
|
||||
const data = ref<Encounter>(genEncounter())
|
||||
const isShowPatient = computed(() => data.value && data.value?.patient?.person)
|
||||
|
||||
async function fetchDetail() {
|
||||
const res = await getDetail(id, {
|
||||
includes: 'patient,patient-person,patient-person-addresses,unit,Appointment_Doctor,Appointment_Doctor-employee,Appointment_Doctor-employee-person,EncounterDocuments',
|
||||
})
|
||||
if(res.body?.data) data.value = res.body?.data
|
||||
const { setOpen } = useSidebar()
|
||||
setOpen(false)
|
||||
|
||||
if (activePosition.value === 'none') { // if user position is none, redirect to home page
|
||||
router.push('/')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchDetail()
|
||||
// Dummy rows for ProtocolList (matches keys expected by list-cfg.protocol)
|
||||
const protocolRows = [
|
||||
{
|
||||
number: '1',
|
||||
tanggal: new Date().toISOString().substring(0, 10),
|
||||
siklus: 'I',
|
||||
periode: 'Siklus I',
|
||||
kehadiran: 'Hadir',
|
||||
action: '',
|
||||
},
|
||||
{
|
||||
number: '2',
|
||||
tanggal: new Date().toISOString().substring(0, 10),
|
||||
siklus: 'II',
|
||||
periode: 'Siklus II',
|
||||
kehadiran: 'Tidak Hadir',
|
||||
action: '',
|
||||
},
|
||||
]
|
||||
|
||||
const paginationMeta = {
|
||||
recordCount: protocolRows.length,
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
totalPage: 1,
|
||||
hasNext: false,
|
||||
hasPrev: false,
|
||||
}
|
||||
|
||||
// Reacrtivities
|
||||
watch(getActiveRole, () => {
|
||||
activePosition.value = getServicePosition(userActiveRole)
|
||||
initMenus()
|
||||
})
|
||||
|
||||
const tabs: TabItem[] = [
|
||||
{ value: 'status', label: 'Status Masuk/Keluar', component: Status, props: { encounter: data } },
|
||||
{
|
||||
value: 'early-medical-assessment',
|
||||
label: 'Pengkajian Awal Medis',
|
||||
component: EarlyMedicalAssesmentList,
|
||||
props: { encounter: data, type: 'early-medic', label: 'Pengkajian Awal Medis' },
|
||||
},
|
||||
{
|
||||
value: 'rehab-medical-assessment',
|
||||
label: 'Pengkajian Awal Medis Rehabilitasi Medis',
|
||||
component: EarlyMedicalRehabList,
|
||||
props: { encounter: data, type: 'early-rehab', label: 'Pengkajian Awal Medis Rehabilitasi Medis' },
|
||||
},
|
||||
{
|
||||
value: 'kfr',
|
||||
label: 'Formulir Rawat Jalan KFR',
|
||||
component: KfrList,
|
||||
props: { encounter: data, type: 'kfr', label: 'Formulir Rawat Jalan KFR' },
|
||||
},
|
||||
{
|
||||
value: 'function-assessment',
|
||||
label: 'Asesmen Fungsi',
|
||||
component: AssesmentFunctionList,
|
||||
props: { encounter: data, type: 'function', label: 'Asesmen Fungsi' },
|
||||
},
|
||||
{ value: 'therapy-protocol', label: 'Protokol Terapi', component: TherapyProtocolList, props: { encounter: data } },
|
||||
{ value: 'education-assessment', label: 'Asesmen Kebutuhan Edukasi' },
|
||||
{ value: 'consent', label: 'General Consent', component: GeneralConsentList, props: { encounter: data } },
|
||||
{ value: 'patient-note', label: 'CPRJ' },
|
||||
{ value: 'prescription', label: 'Order Obat', component: Prescription, props: { encounter_id: data.value.id } },
|
||||
{ value: 'device-order', label: 'Order Alkes', component: DeviceOrder, props: { encounter_id: data.value.id } },
|
||||
{ value: 'mcu-radiology', label: 'Order Radiologi', component: Radiology, props: { encounter_id: data.value.id } },
|
||||
{ value: 'mcu-lab-cp', label: 'Order Lab PK', component: CpLabOrder, props: { encounter_id: data.value.id } },
|
||||
{ value: 'mcu-lab-micro', label: 'Order Lab Mikro' },
|
||||
{ value: 'mcu-lab-pa', label: 'Order Lab PA' },
|
||||
{ value: 'medical-action', label: 'Order Ruang Tindakan' },
|
||||
{ value: 'mcu-result', label: 'Hasil Penunjang' },
|
||||
{ value: 'consultation', label: 'Konsultasi', component: Consultation, props: { encounter: data } },
|
||||
{ value: 'resume', label: 'Resume', component: ResumeList, props: { encounter: data } },
|
||||
{ value: 'control', label: 'Surat Kontrol', component: ControlLetterList, props: { encounter: data } },
|
||||
{ value: 'screening', label: 'Skrinning MPP' },
|
||||
{ value: 'supporting-document', label: 'Upload Dokumen Pendukung', component: DocUploadList, props: { encounter: data }, },
|
||||
]
|
||||
onMounted(async () => {
|
||||
await getData()
|
||||
initMenus()
|
||||
})
|
||||
|
||||
///// Functions
|
||||
function handleClick(type: string) {
|
||||
if (type === 'draft') {
|
||||
router.back()
|
||||
}
|
||||
}
|
||||
|
||||
function initMenus() {
|
||||
menus.value = getMenuItems(id, props, user, {
|
||||
encounter: data.value
|
||||
} as any, {
|
||||
protocolTheraphy: paginationMeta,
|
||||
protocolChemotherapy: paginationMeta,
|
||||
medicineProtocolChemotherapy: paginationMeta,
|
||||
})
|
||||
}
|
||||
|
||||
async function getData() {
|
||||
data.value = await getEncounterData(id)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full">
|
||||
<div class="mb-4">
|
||||
<PubMyUiNavContentBa label="Kembali ke Daftar Kunjungan" />
|
||||
<div class="bg-white dark:bg-slate-800 p-4 2xl:p-5">
|
||||
<div class="mb-4 flex">
|
||||
<div>
|
||||
<ContentNavBa label="Kembali" @click="handleClick" />
|
||||
</div>
|
||||
<!-- <div class="ms-auto pe-3 pt-1 text-end text-xl 2xl:text-2xl font-semibold">
|
||||
Pasien: {{ data.patient.person.name }} --- No. RM: {{ data.patient.number }}
|
||||
</div> -->
|
||||
</div>
|
||||
<ContentSwitcher :active="1" :height="150">
|
||||
<template v-slot:content1>
|
||||
<EncounterPatientInfo v-if="isShowPatient" :data="data" />
|
||||
</template>
|
||||
<template v-slot:content2>
|
||||
<EncounterHistoryButtonMenu v-if="isShowPatient" />
|
||||
</template>
|
||||
</ContentSwitcher>
|
||||
</div>
|
||||
<AppEncounterQuickInfo :data="data" />
|
||||
<CompTab :data="tabs" :initial-active-tab="activeTab" @change-tab="activeTab = $event" />
|
||||
<SubMenu :data="menus" :initial-active-menu="activeMenu" @change-menu="activeMenu = $event" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,483 +1,59 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { onMounted } from 'vue'
|
||||
|
||||
// Components
|
||||
import AppSepEntryForm from '~/components/app/sep/entry-form.vue'
|
||||
import AppViewPatient from '~/components/app/patient/view-patient.vue'
|
||||
import AppViewHistory from '~/components/app/sep/view-history.vue'
|
||||
import AppViewLetter from '~/components/app/sep/view-letter.vue'
|
||||
import { toast } from '~/components/pub/ui/toast'
|
||||
|
||||
// Types
|
||||
import type { SepHistoryData } from '~/components/app/sep/list-cfg.history'
|
||||
import type { TreeItem } from '~/components/pub/my-ui/select-tree/type'
|
||||
// Handler
|
||||
import useIntegrationSepEntry from '~/handlers/integration-sep-entry.handler'
|
||||
|
||||
// Constants
|
||||
import {
|
||||
serviceTypes,
|
||||
serviceAssessments,
|
||||
registerMethods,
|
||||
trafficAccidents,
|
||||
supportCodes,
|
||||
procedureTypes,
|
||||
purposeOfVisits,
|
||||
classLevels,
|
||||
classLevelUpgrades,
|
||||
classPaySources,
|
||||
} from '~/lib/constants.vclaim'
|
||||
|
||||
// Services
|
||||
import {
|
||||
getList as getSpecialistList,
|
||||
getValueTreeItems as getSpecialistTreeItems,
|
||||
} from '~/services/specialist.service'
|
||||
import { getValueLabelList as getProvinceList } from '~/services/vclaim-region-province.service'
|
||||
import { getValueLabelList as getCityList } from '~/services/vclaim-region-city.service'
|
||||
import { getValueLabelList as getDistrictList } from '~/services/vclaim-region-district.service'
|
||||
import { getValueLabelList as getDoctorLabelList } from '~/services/vclaim-doctor.service'
|
||||
import { getValueLabelList as getHealthFacilityLabelList } from '~/services/vclaim-healthcare.service'
|
||||
import { getValueLabelList as getDiagnoseLabelList } from '~/services/vclaim-diagnose.service'
|
||||
import { getList as getMemberList } from '~/services/vclaim-member.service'
|
||||
import { getList as getHospitalLetterList } from '~/services/vclaim-reference-hospital-letter.service'
|
||||
import { getList as getControlLetterList } from '~/services/vclaim-control-letter.service'
|
||||
import { getList as getMonitoringHistoryList } from '~/services/vclaim-monitoring-history.service'
|
||||
import { create as createSep, makeSepData } from '~/services/vclaim-sep.service'
|
||||
|
||||
// Handlers
|
||||
import {
|
||||
const {
|
||||
histories,
|
||||
letters,
|
||||
patients,
|
||||
doctors,
|
||||
diagnoses,
|
||||
facilitiesFrom,
|
||||
facilitiesTo,
|
||||
supportCodesList,
|
||||
serviceTypesList,
|
||||
registerMethodsList,
|
||||
accidentsList,
|
||||
purposeOfVisitsList,
|
||||
proceduresList,
|
||||
assessmentsList,
|
||||
provincesList,
|
||||
citiesList,
|
||||
districtsList,
|
||||
classLevelsList,
|
||||
classLevelUpgradesList,
|
||||
classPaySourcesList,
|
||||
isServiceHidden,
|
||||
isSaveLoading,
|
||||
isLetterReadonly,
|
||||
isLoadingPatient,
|
||||
openPatient,
|
||||
openLetter,
|
||||
openHistory,
|
||||
selectedLetter,
|
||||
selectedObjects,
|
||||
selectedServiceType,
|
||||
selectedAdmissionType,
|
||||
specialistsTree,
|
||||
selectedPatient,
|
||||
selectedPatientObject,
|
||||
paginationMeta,
|
||||
getLetterMappers,
|
||||
getPatientsList,
|
||||
getPatientCurrent,
|
||||
getPatientByIdentifierSearch,
|
||||
} from '~/handlers/patient.handler'
|
||||
|
||||
const route = useRoute()
|
||||
const openPatient = ref(false)
|
||||
const openLetter = ref(false)
|
||||
const openHistory = ref(false)
|
||||
const selectedLetter = ref('')
|
||||
const selectedObjects = ref<any>({})
|
||||
const selectedServiceType = ref<string>('')
|
||||
const selectedAdmissionType = ref<string>('')
|
||||
const histories = ref<Array<SepHistoryData>>([])
|
||||
const letters = ref<Array<any>>([])
|
||||
const doctors = ref<Array<{ value: string | number; label: string }>>([])
|
||||
const diagnoses = ref<Array<{ value: string | number; label: string }>>([])
|
||||
const facilitiesFrom = ref<Array<{ value: string | number; label: string }>>([])
|
||||
const facilitiesTo = ref<Array<{ value: string | number; label: string }>>([])
|
||||
const supportCodesList = ref<Array<{ value: string; label: string }>>([])
|
||||
const serviceTypesList = ref<Array<{ value: string; label: string }>>([])
|
||||
const registerMethodsList = ref<Array<{ value: string; label: string }>>([])
|
||||
const accidentsList = ref<Array<{ value: string; label: string }>>([])
|
||||
const purposeOfVisitsList = ref<Array<{ value: string; label: string }>>([])
|
||||
const proceduresList = ref<Array<{ value: string; label: string }>>([])
|
||||
const assessmentsList = ref<Array<{ value: string; label: string }>>([])
|
||||
const provincesList = ref<Array<{ value: string; label: string }>>([])
|
||||
const citiesList = ref<Array<{ value: string; label: string }>>([])
|
||||
const districtsList = ref<Array<{ value: string; label: string }>>([])
|
||||
const classLevelsList = ref<Array<{ value: string; label: string }>>([])
|
||||
const classLevelUpgradesList = ref<Array<{ value: string; label: string }>>([])
|
||||
const classPaySourcesList = ref<Array<{ value: string; label: string }>>([])
|
||||
const isServiceHidden = ref(false)
|
||||
const isSaveLoading = ref(false)
|
||||
const isLetterReadonly = ref(false)
|
||||
const specialistsTree = ref<TreeItem[]>([])
|
||||
const resourceType = ref('')
|
||||
const resourcePath = ref('')
|
||||
|
||||
async function getMonitoringHistoryMappers() {
|
||||
histories.value = []
|
||||
const dateFirst = new Date()
|
||||
const dateLast = new Date()
|
||||
dateLast.setMonth(dateFirst.getMonth() - 3)
|
||||
const cardNumber =
|
||||
selectedPatientObject.value?.person?.residentIdentityNumber || selectedPatientObject.value?.number || ''
|
||||
const result = await getMonitoringHistoryList({
|
||||
cardNumber: cardNumber,
|
||||
startDate: dateFirst.toISOString().substring(0, 10),
|
||||
endDate: dateLast.toISOString().substring(0, 10),
|
||||
})
|
||||
if (result && result.success && result.body) {
|
||||
const historiesRaw = result.body?.response?.histori || []
|
||||
if (!historiesRaw) return
|
||||
historiesRaw.forEach((result: any) => {
|
||||
histories.value.push({
|
||||
sepNumber: result.noSep,
|
||||
sepDate: result.tglSep,
|
||||
referralNumber: result.noRujukan,
|
||||
diagnosis:
|
||||
result.diagnosa && typeof result.diagnosa === 'string' && result.diagnosa.length > 20
|
||||
? result.diagnosa.toString().substring(0, 17) + '...'
|
||||
: '-',
|
||||
serviceType: !result.jnsPelayanan ? '-' : result.jnsPelayanan === '1' ? 'Rawat Jalan' : 'Rawat Inap',
|
||||
careClass: result.kelasRawat,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function getLetterMappers(admissionType: string, search: string) {
|
||||
letters.value = []
|
||||
let result = null
|
||||
if (admissionType !== '3') {
|
||||
result = await getHospitalLetterList({
|
||||
letterNumber: search,
|
||||
})
|
||||
} else {
|
||||
result = await getControlLetterList({
|
||||
letterNumber: search,
|
||||
mode: 'by-control',
|
||||
})
|
||||
if (result && result.success && result.body) {
|
||||
const lettersRaw = result.body?.response || null
|
||||
if (!lettersRaw) {
|
||||
result = await getControlLetterList({
|
||||
letterNumber: search,
|
||||
mode: 'by-card',
|
||||
})
|
||||
}
|
||||
}
|
||||
if (result && result.success && result.body) {
|
||||
const lettersRaw = result.body?.response || null
|
||||
if (!lettersRaw) {
|
||||
result = await getControlLetterList({
|
||||
letterNumber: search,
|
||||
mode: 'by-sep',
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
if (result && result.success && result.body) {
|
||||
const lettersRaw = result.body?.response || null
|
||||
if (!lettersRaw) return
|
||||
if (admissionType === '3') {
|
||||
letters.value = [
|
||||
{
|
||||
letterNumber: lettersRaw.noSuratKontrol || '',
|
||||
plannedDate: lettersRaw.tglRencanaKontrol || '',
|
||||
sepNumber: lettersRaw.sep.noSep || '',
|
||||
patientName: lettersRaw.sep.peserta.nama || '',
|
||||
bpjsCardNo: lettersRaw.sep.peserta.noKartu,
|
||||
clinic: lettersRaw.sep.poli || '',
|
||||
doctor: lettersRaw.sep.namaDokter || '',
|
||||
},
|
||||
]
|
||||
} else {
|
||||
// integrate ke sep ---
|
||||
// "rujukan": {
|
||||
// "noRujukan": "0212R0300625B000006", // rujukan?.noKunjungan
|
||||
// "ppkRujukan": "0212R030",
|
||||
// "tglRujukan": "2025-06-26",
|
||||
// "asalRujukan": "2" // asalFaskes
|
||||
// },
|
||||
// "jnsPelayanan": "2",
|
||||
// "ppkPelayanan": "1323R001",
|
||||
// "poli": {
|
||||
// "tujuan": "URO", // rujukan?.poliRujukan?.kode
|
||||
// },
|
||||
// "klsRawat": {
|
||||
// "pembiayaan": "",
|
||||
// "klsRawatHak": "2", // peserta.hakKelas?.kode
|
||||
// "klsRawatNaik": "",
|
||||
// "penanggungJawab": ""
|
||||
// },
|
||||
|
||||
letters.value = [
|
||||
{
|
||||
letterNumber: lettersRaw?.rujukan?.noKunjungan || '',
|
||||
plannedDate: lettersRaw?.rujukan?.tglKunjungan || '',
|
||||
sepNumber: lettersRaw?.rujukan?.informasi?.eSEP || '-',
|
||||
patientName: lettersRaw?.rujukan?.peserta.nama || '',
|
||||
bpjsCardNo: lettersRaw?.rujukan?.peserta.noKartu || '',
|
||||
clinic: lettersRaw?.rujukan?.poliRujukan.nama || '',
|
||||
doctor: '',
|
||||
information: {
|
||||
facility: lettersRaw?.asalFaskes || '',
|
||||
diagnoses: lettersRaw?.rujukan?.diagnosa?.kode || '',
|
||||
serviceType: lettersRaw?.rujukan?.pelayanan?.kode || '',
|
||||
classLevel: lettersRaw?.rujukan?.peserta?.hakKelas?.kode || '',
|
||||
poly: lettersRaw?.rujukan?.poliRujukan?.kode || '',
|
||||
cardNumber: lettersRaw?.rujukan?.peserta?.noKartu || '',
|
||||
patientName: lettersRaw?.rujukan?.peserta?.nama || '',
|
||||
patientPhone: lettersRaw?.rujukan?.peserta?.mr?.noTelepon || '',
|
||||
medicalRecordNumber: lettersRaw?.rujukan?.peserta?.mr?.noMR || '',
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function getPatientInternalMappers(id: string) {
|
||||
try {
|
||||
await getPatientCurrent(id)
|
||||
if (selectedPatientObject.value) {
|
||||
const patient = selectedPatientObject.value
|
||||
selectedObjects.value['cardNumber'] = '-'
|
||||
selectedObjects.value['nationalIdentity'] = patient?.person?.residentIdentityNumber || '-'
|
||||
selectedObjects.value['medicalRecordNumber'] = patient?.number || '-'
|
||||
selectedObjects.value['patientName'] = patient?.person?.name || '-'
|
||||
selectedObjects.value['phoneNumber'] = patient?.person?.contacts?.[0]?.value || '-'
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to load patient from query params:', err)
|
||||
}
|
||||
}
|
||||
|
||||
async function getPatientExternalMappers(id: string, type: string) {
|
||||
try {
|
||||
const result = await getMemberList({
|
||||
mode: type,
|
||||
number: id,
|
||||
date: new Date().toISOString().substring(0, 10),
|
||||
})
|
||||
if (result && result.success && result.body) {
|
||||
const memberRaws = result.body?.response || null
|
||||
selectedObjects.value['cardNumber'] = memberRaws?.peserta?.noKartu || ''
|
||||
selectedObjects.value['nationalIdentity'] = memberRaws?.peserta?.nik || ''
|
||||
selectedObjects.value['medicalRecordNumber'] = memberRaws?.peserta?.mr?.noMR || ''
|
||||
selectedObjects.value['patientName'] = memberRaws?.peserta?.nama || ''
|
||||
selectedObjects.value['phoneNumber'] = memberRaws?.peserta?.mr?.noTelepon || ''
|
||||
selectedObjects.value['classLevel'] = memberRaws?.peserta?.hakKelas?.kode || ''
|
||||
selectedObjects.value['status'] = memberRaws?.statusPeserta?.kode || ''
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to load patient from query params:', err)
|
||||
}
|
||||
}
|
||||
|
||||
function handleSaveLetter() {
|
||||
// Find the selected letter and get its plannedDate
|
||||
const selectedLetterData = letters.value.find((letter) => letter.letterNumber === selectedLetter.value)
|
||||
if (selectedLetterData && selectedLetterData.plannedDate) {
|
||||
selectedObjects.value['letterDate'] = selectedLetterData.plannedDate
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSavePatient() {
|
||||
selectedPatientObject.value = null
|
||||
await getPatientInternalMappers(selectedPatient.value)
|
||||
}
|
||||
|
||||
async function handleEvent(menu: string, value: any) {
|
||||
if (menu === 'admission-type') {
|
||||
selectedAdmissionType.value = value
|
||||
return
|
||||
}
|
||||
if (menu === 'service-type') {
|
||||
selectedServiceType.value = value
|
||||
doctors.value = await getDoctorLabelList({
|
||||
serviceType: selectedServiceType.value || 1,
|
||||
serviceDate: new Date().toISOString().substring(0, 10),
|
||||
specialistCode: 0,
|
||||
})
|
||||
}
|
||||
if (menu === 'search-patient') {
|
||||
getPatientsList({ 'page-size': 10, includes: 'person' }).then(() => {
|
||||
openPatient.value = true
|
||||
})
|
||||
return
|
||||
}
|
||||
if (menu === 'search-patient-by-identifier') {
|
||||
const text = value.text
|
||||
const type = value.type
|
||||
const prevCardNumber = selectedPatientObject.value?.person?.residentIdentityNumber || ''
|
||||
if (type === 'indentity' && text !== prevCardNumber) {
|
||||
await getPatientByIdentifierSearch(text)
|
||||
await getPatientExternalMappers(text, 'by-identity')
|
||||
}
|
||||
if (type === 'cardNumber' && text !== prevCardNumber) {
|
||||
await getPatientExternalMappers(text, 'by-card')
|
||||
}
|
||||
return
|
||||
}
|
||||
if (menu === 'search-letter') {
|
||||
isLetterReadonly.value = false
|
||||
getLetterMappers(value.admissionType, value.search).then(() => {
|
||||
if (letters.value.length > 0) {
|
||||
const copyObjects = { ...selectedObjects.value }
|
||||
selectedObjects.value = {}
|
||||
selectedLetter.value = letters.value[0].letterNumber
|
||||
isLetterReadonly.value = true
|
||||
setTimeout(() => {
|
||||
selectedObjects.value = copyObjects
|
||||
selectedObjects.value['letterDate'] = letters.value[0].plannedDate
|
||||
}, 100)
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
if (menu === 'open-letter') {
|
||||
openLetter.value = true
|
||||
return
|
||||
}
|
||||
if (menu === 'history-sep') {
|
||||
getMonitoringHistoryMappers().then(() => {
|
||||
openHistory.value = true
|
||||
})
|
||||
return
|
||||
}
|
||||
if (menu === 'back') {
|
||||
navigateTo('/integration/bpjs/sep')
|
||||
}
|
||||
if (menu === 'save-sep') {
|
||||
isSaveLoading.value = true
|
||||
// value.destinationClinic = value.destinationClinic || ''
|
||||
createSep(makeSepData(value))
|
||||
.then((res) => {
|
||||
const body = res?.body
|
||||
const code = body?.metaData?.code
|
||||
const message = body?.metaData?.message
|
||||
if (code && code !== '200') {
|
||||
toast({ title: 'Gagal', description: message || 'Gagal membuat SEP', variant: 'destructive' })
|
||||
return
|
||||
}
|
||||
toast({ title: 'Berhasil', description: 'SEP berhasil dibuat', variant: 'default' })
|
||||
if (resourceType.value === 'encounter') {
|
||||
navigateTo(resourcePath.value)
|
||||
return
|
||||
}
|
||||
navigateTo('/integration/bpjs/sep')
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Failed to save SEP:', err)
|
||||
toast({ title: 'Gagal', description: err?.message || 'Gagal membuat SEP', variant: 'destructive' })
|
||||
})
|
||||
.finally(() => {
|
||||
isSaveLoading.value = false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function handleFetch(params: any) {
|
||||
const menu = params.menu || ''
|
||||
const value = params.value || ''
|
||||
if (menu === 'diagnosis') {
|
||||
diagnoses.value = await getDiagnoseLabelList({ diagnosa: value })
|
||||
}
|
||||
if (menu === 'clinic-from') {
|
||||
facilitiesFrom.value = await getHealthFacilityLabelList({
|
||||
healthcare: value,
|
||||
healthcareType: selectedServiceType.value || 1,
|
||||
})
|
||||
}
|
||||
if (menu === 'clinic-to') {
|
||||
facilitiesTo.value = await getHealthFacilityLabelList({
|
||||
healthcare: value,
|
||||
healthcareType: selectedServiceType.value || 1,
|
||||
})
|
||||
}
|
||||
if (menu === 'province') {
|
||||
citiesList.value = await getCityList({ province: value })
|
||||
districtsList.value = []
|
||||
}
|
||||
if (menu === 'city') {
|
||||
districtsList.value = await getDistrictList({ city: value })
|
||||
}
|
||||
}
|
||||
|
||||
async function handleFetchSpecialists() {
|
||||
try {
|
||||
const specialistsResult = await getSpecialistList({ 'page-size': 100, includes: 'subspecialists' })
|
||||
if (specialistsResult.success) {
|
||||
const specialists = specialistsResult.body?.data || []
|
||||
specialistsTree.value = getSpecialistTreeItems(specialists)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching specialist-subspecialist tree:', error)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleInit() {
|
||||
const facilities = await getHealthFacilityLabelList({
|
||||
healthcare: 'Puskesmas',
|
||||
healthcareType: selectedLetter.value || 1,
|
||||
})
|
||||
diagnoses.value = await getDiagnoseLabelList({ diagnosa: 'paru' })
|
||||
facilitiesFrom.value = facilities
|
||||
facilitiesTo.value = facilities
|
||||
doctors.value = await getDoctorLabelList({
|
||||
serviceType: selectedServiceType.value || 1,
|
||||
serviceDate: new Date().toISOString().substring(0, 10),
|
||||
specialistCode: 0,
|
||||
})
|
||||
provincesList.value = await getProvinceList()
|
||||
serviceTypesList.value = Object.keys(serviceTypes).map((item) => ({
|
||||
value: item.toString(),
|
||||
label: serviceTypes[item],
|
||||
})) as any
|
||||
registerMethodsList.value = Object.keys(registerMethods)
|
||||
.filter((item) => ![''].includes(item))
|
||||
.map((item) => ({
|
||||
value: item.toString(),
|
||||
label: registerMethods[item],
|
||||
})) as any
|
||||
accidentsList.value = Object.keys(trafficAccidents).map((item) => ({
|
||||
value: item.toString(),
|
||||
label: trafficAccidents[item],
|
||||
})) as any
|
||||
purposeOfVisitsList.value = Object.keys(purposeOfVisits).map((item) => ({
|
||||
value: item.toString(),
|
||||
label: purposeOfVisits[item],
|
||||
})) as any
|
||||
proceduresList.value = Object.keys(procedureTypes).map((item) => ({
|
||||
value: item.toString(),
|
||||
label: procedureTypes[item],
|
||||
})) as any
|
||||
assessmentsList.value = Object.keys(serviceAssessments).map((item) => ({
|
||||
value: item.toString(),
|
||||
label: `${item.toString()} - ${serviceAssessments[item]}`,
|
||||
})) as any
|
||||
supportCodesList.value = Object.keys(supportCodes).map((item) => ({
|
||||
value: item.toString(),
|
||||
label: `${item.toString()} - ${supportCodes[item]}`,
|
||||
})) as any
|
||||
classLevelsList.value = Object.keys(classLevels).map((item) => ({
|
||||
value: item.toString(),
|
||||
label: classLevels[item],
|
||||
})) as any
|
||||
classLevelUpgradesList.value = Object.keys(classLevelUpgrades).map((item) => ({
|
||||
value: item.toString(),
|
||||
label: classLevelUpgrades[item],
|
||||
})) as any
|
||||
classPaySourcesList.value = Object.keys(classPaySources).map((item) => ({
|
||||
value: item.toString(),
|
||||
label: classPaySources[item],
|
||||
})) as any
|
||||
await handleFetchSpecialists()
|
||||
if (route.query) {
|
||||
const queries = route.query as any
|
||||
isServiceHidden.value = queries['is-service'] === 'true'
|
||||
selectedObjects.value = {}
|
||||
if (queries['resource']) resourceType.value = queries['resource']
|
||||
if (queries['resource-path']) resourcePath.value = queries['resource-path']
|
||||
if (queries['doctor-code']) selectedObjects.value['doctorCode'] = queries['doctor-code']
|
||||
if (queries['specialist-code']) selectedObjects.value['subSpecialistCode'] = queries['specialist-code']
|
||||
if (queries['sub-specialist-code']) selectedObjects.value['subSpecialistCode'] = queries['sub-specialist-code']
|
||||
if (queries['card-number']) selectedObjects.value['cardNumber'] = queries['card-number']
|
||||
if (queries['register-date']) selectedObjects.value['registerDate'] = queries['register-date']
|
||||
if (queries['sep-type']) selectedObjects.value['sepType'] = queries['sep-type']
|
||||
if (queries['sep-number']) selectedObjects.value['sepNumber'] = queries['sep-number']
|
||||
if (queries['register-date']) selectedObjects.value['registerDate'] = queries['register-date']
|
||||
if (queries['payment-type']) selectedObjects.value['paymentType'] = queries['payment-type']
|
||||
if (queries['patient-id']) {
|
||||
await getPatientInternalMappers(queries['patient-id'])
|
||||
}
|
||||
if (queries['card-number']) {
|
||||
const resultMember = await getMemberList({
|
||||
mode: 'by-card',
|
||||
number: queries['card-number'],
|
||||
date: new Date().toISOString().substring(0, 10),
|
||||
})
|
||||
console.log(resultMember)
|
||||
}
|
||||
delete selectedObjects.value['is-service']
|
||||
}
|
||||
}
|
||||
handleSaveLetter,
|
||||
handleSavePatient,
|
||||
handleEvent,
|
||||
handleFetch,
|
||||
handleInit,
|
||||
} = useIntegrationSepEntry()
|
||||
|
||||
onMounted(async () => {
|
||||
await handleInit()
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
// Components
|
||||
import type { DataTableLoader } from '~/components/pub/my-ui/data-table/type'
|
||||
import type { HeaderPrep, RefSearchNav } from '~/components/pub/my-ui/data/types'
|
||||
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
|
||||
import Header from '~/components/pub/my-ui/nav-header/prep.vue'
|
||||
import {
|
||||
DropdownMenu,
|
||||
@@ -11,141 +8,48 @@ import {
|
||||
DropdownMenuItem,
|
||||
} from '~/components/pub/ui/dropdown-menu'
|
||||
import AppSepList from '~/components/app/sep/list.vue'
|
||||
import RangeCalendar from '~/components/pub/ui/range-calendar/RangeCalendar.vue'
|
||||
|
||||
// Icons
|
||||
import { X, Check } from 'lucide-vue-next'
|
||||
|
||||
// Types
|
||||
import type { VclaimSepData } from '~/models/vclaim'
|
||||
// Libraries
|
||||
import useIntegrationSepList from '~/handlers/integration-sep-list.handler'
|
||||
|
||||
// Services
|
||||
import { getList as geMonitoringVisitList } from '~/services/vclaim-monitoring-visit.service'
|
||||
// use handler to provide state and functions
|
||||
const {
|
||||
recId,
|
||||
recAction,
|
||||
recItem,
|
||||
data,
|
||||
dateSelection,
|
||||
dateRange,
|
||||
serviceType,
|
||||
serviceTypesList,
|
||||
search,
|
||||
open,
|
||||
sepData,
|
||||
headerPrep,
|
||||
paginationMeta,
|
||||
isLoading,
|
||||
getSepList,
|
||||
setServiceTypes,
|
||||
setDateRange,
|
||||
handleExportCsv,
|
||||
handleExportExcel,
|
||||
handleRowSelected,
|
||||
handlePageChange,
|
||||
handleRemove,
|
||||
} = useIntegrationSepList()
|
||||
|
||||
const search = ref('')
|
||||
const dateRange = ref('12 Agustus 2025 - 31 Agustus 2025')
|
||||
const open = ref(false)
|
||||
|
||||
const sepData = {
|
||||
no_sep: 'SP23311224',
|
||||
kartu: '001234',
|
||||
nama: 'Kenzie',
|
||||
}
|
||||
|
||||
const paginationMeta = reactive<PaginationMeta>({
|
||||
recordCount: 0,
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
totalPage: 5,
|
||||
hasNext: false,
|
||||
hasPrev: false,
|
||||
})
|
||||
|
||||
function handlePageChange(page: number) {
|
||||
console.log('pageChange', page)
|
||||
}
|
||||
|
||||
const data = ref<VclaimSepData[]>([])
|
||||
|
||||
const refSearchNav: RefSearchNav = {
|
||||
onClick: () => {
|
||||
// open filter modal
|
||||
},
|
||||
onInput: (_val: string) => {
|
||||
// filter patient list
|
||||
},
|
||||
onClear: () => {
|
||||
// clear url param
|
||||
},
|
||||
}
|
||||
|
||||
const isLoading = reactive<DataTableLoader>({
|
||||
isTableLoading: false,
|
||||
})
|
||||
|
||||
const recId = ref<number>(0)
|
||||
const recAction = ref<string>('')
|
||||
const recItem = ref<any>(null)
|
||||
|
||||
const headerPrep: HeaderPrep = {
|
||||
title: 'Daftar SEP Prosedur',
|
||||
icon: 'i-lucide-panel-bottom',
|
||||
addNav: {
|
||||
label: 'Tambah',
|
||||
onClick: () => {
|
||||
navigateTo('/integration/bpjs/sep/add')
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
async function getMonitoringVisitMappers() {
|
||||
isLoading.dataListLoading = true
|
||||
data.value = []
|
||||
|
||||
const dateFirst = new Date()
|
||||
const result = await geMonitoringVisitList({
|
||||
date: dateFirst.toISOString().substring(0, 10),
|
||||
serviceType: 1,
|
||||
})
|
||||
|
||||
if (result && result.success && result.body) {
|
||||
const visitsRaw = result.body?.response?.sep || []
|
||||
|
||||
if (!visitsRaw) {
|
||||
isLoading.dataListLoading = false
|
||||
return
|
||||
}
|
||||
|
||||
visitsRaw.forEach((result: any) => {
|
||||
// Format pelayanan: "R.Inap" -> "Rawat Inap", "1" -> "Rawat Jalan", dll
|
||||
let serviceType = result.jnsPelayanan || '-'
|
||||
if (serviceType === 'R.Inap') {
|
||||
serviceType = 'Rawat Inap'
|
||||
} else if (serviceType === '1' || serviceType === 'R.Jalan') {
|
||||
serviceType = 'Rawat Jalan'
|
||||
}
|
||||
|
||||
data.value.push({
|
||||
letterDate: result.tglSep || '-',
|
||||
letterNumber: result.noSep || '-',
|
||||
serviceType: serviceType,
|
||||
flow: '-',
|
||||
medicalRecordNumber: '-',
|
||||
patientName: result.nama || '-',
|
||||
cardNumber: result.noKartu || '-',
|
||||
controlLetterNumber: result.noRujukan || '-',
|
||||
controlLetterDate: result.tglPlgSep || '-',
|
||||
clinicDestination: result.poli || '-',
|
||||
attendingDoctor: '-',
|
||||
diagnosis: result.diagnosa || '-',
|
||||
careClass: result.kelasRawat || '-',
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
isLoading.dataListLoading = false
|
||||
}
|
||||
|
||||
async function getSepList() {
|
||||
await getMonitoringVisitMappers()
|
||||
}
|
||||
|
||||
function exportCsv() {
|
||||
console.log('Ekspor CSV dipilih')
|
||||
// tambahkan logic untuk generate CSV
|
||||
}
|
||||
|
||||
function exportExcel() {
|
||||
console.log('Ekspor Excel dipilih')
|
||||
// tambahkan logic untuk generate Excel
|
||||
}
|
||||
|
||||
function handleDelete() {
|
||||
console.log('Data dihapus:', sepData)
|
||||
open.value = false
|
||||
}
|
||||
// expose provides so component can also use provide/inject if needed
|
||||
provide('rec_id', recId)
|
||||
provide('rec_action', recAction)
|
||||
provide('rec_item', recItem)
|
||||
provide('table_data_loader', isLoading)
|
||||
|
||||
watch(
|
||||
() => recAction.value,
|
||||
[recId, recAction],
|
||||
() => {
|
||||
if (recAction.value === 'showConfirmDel') {
|
||||
open.value = true
|
||||
@@ -153,14 +57,26 @@ watch(
|
||||
},
|
||||
)
|
||||
|
||||
watch(
|
||||
() => dateSelection.value,
|
||||
(val) => {
|
||||
if (!val) return
|
||||
setDateRange()
|
||||
},
|
||||
{ deep: true },
|
||||
)
|
||||
|
||||
watch(
|
||||
() => serviceType.value,
|
||||
() => {
|
||||
getSepList()
|
||||
},
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
setServiceTypes()
|
||||
getSepList()
|
||||
})
|
||||
|
||||
provide('rec_id', recId)
|
||||
provide('rec_action', recAction)
|
||||
provide('rec_item', recItem)
|
||||
provide('table_data_loader', isLoading)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -169,100 +85,77 @@ provide('table_data_loader', isLoading)
|
||||
<!-- Filter Bar -->
|
||||
<div class="my-2 flex flex-wrap items-center gap-2">
|
||||
<!-- Search -->
|
||||
<Input
|
||||
v-model="search"
|
||||
placeholder="Cari No. SEP / No. Kartu BPJS..."
|
||||
class="w-72"
|
||||
/>
|
||||
<Input v-model="search" placeholder="Cari No. SEP / No. Kartu BPJS..." class="w-72" />
|
||||
|
||||
<!-- Filter -->
|
||||
<div class="w-72">
|
||||
<Select id="serviceType" icon-name="i-lucide-chevron-down" v-model="serviceType" :items="serviceTypesList"
|
||||
placeholder="Pilih Pelayanan" />
|
||||
</div>
|
||||
|
||||
<!-- Date Range -->
|
||||
<Popover>
|
||||
<PopoverTrigger as-child>
|
||||
<Button
|
||||
variant="outline"
|
||||
class="h-[40px] w-72 border-gray-400 bg-white text-right font-normal"
|
||||
>
|
||||
<Button variant="outline" class="h-[40px] w-72 border-gray-400 bg-white text-right font-normal">
|
||||
{{ dateRange }}
|
||||
<Icon
|
||||
name="i-lucide-calendar"
|
||||
class="h-5 w-5"
|
||||
/>
|
||||
<Icon name="i-lucide-calendar" class="h-5 w-5" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="p-2">
|
||||
<Calendar mode="range" />
|
||||
<RangeCalendar v-model="dateSelection" />
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
||||
<!-- Export -->
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger as-child>
|
||||
<Button
|
||||
variant="outline"
|
||||
class="ml-auto h-[40px] w-[120px] rounded-md border-green-600 text-green-600 hover:bg-green-50"
|
||||
>
|
||||
<Icon
|
||||
name="i-lucide-download"
|
||||
class="h-5 w-5"
|
||||
/>
|
||||
<Button variant="outline"
|
||||
class="ml-auto h-[40px] w-[120px] rounded-md border-green-600 text-green-600 hover:bg-green-50">
|
||||
<Icon name="i-lucide-download" class="h-5 w-5" />
|
||||
Ekspor
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent class="w-40">
|
||||
<DropdownMenuItem @click="exportCsv">Ekspor CSV</DropdownMenuItem>
|
||||
<DropdownMenuItem @click="exportExcel">Ekspor Excel</DropdownMenuItem>
|
||||
<DropdownMenuItem @click="handleExportCsv">Ekspor CSV</DropdownMenuItem>
|
||||
<DropdownMenuItem @click="handleExportExcel">Ekspor Excel</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<AppSepList
|
||||
v-if="!isLoading.dataListLoading"
|
||||
:data="data"
|
||||
/>
|
||||
<AppSepList v-if="!isLoading.dataListLoading" :data="data" @update:modelValue="handleRowSelected" />
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<template v-if="paginationMeta">
|
||||
<div v-if="paginationMeta.totalPage > 1">
|
||||
<PubMyUiPagination
|
||||
:pagination-meta="paginationMeta"
|
||||
@page-change="handlePageChange"
|
||||
/>
|
||||
<PubMyUiPagination :pagination-meta="paginationMeta" @page-change="handlePageChange" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Trigger button -->
|
||||
<Dialog v-model:open="open">
|
||||
<DialogTrigger as-child></DialogTrigger>
|
||||
|
||||
<DialogContent class="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Hapus SEP</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogDescription class="text-gray-700">Apakah anda yakin ingin menghapus SEP dengan data:</DialogDescription>
|
||||
|
||||
<div class="mt-4 space-y-2 text-sm">
|
||||
<p>No. SEP : {{ sepData.no_sep }}</p>
|
||||
<p>No. Kartu BPJS : {{ sepData.kartu }}</p>
|
||||
<p>Nama Pasien : {{ sepData.nama }}</p>
|
||||
<p><strong>No. SEP:</strong> {{ sepData.sepNumber }}</p>
|
||||
<p><strong>No. Kartu BPJS:</strong> {{ sepData.cardNumber }}</p>
|
||||
<p><strong>Nama Pasien:</strong> {{ sepData.patientName }}</p>
|
||||
</div>
|
||||
|
||||
<DialogFooter class="mt-6 flex justify-end gap-3">
|
||||
<Button
|
||||
variant="outline"
|
||||
class="border-green-600 text-green-600 hover:bg-green-50"
|
||||
@click="open = false"
|
||||
>
|
||||
<Button variant="outline" class="border-green-600 text-green-600 hover:bg-green-50" @click="() => {
|
||||
recId = 0
|
||||
recAction = ''
|
||||
open = false
|
||||
}">
|
||||
<X class="mr-1 h-4 w-4" />
|
||||
Tidak
|
||||
</Button>
|
||||
<Button
|
||||
variant="destructive"
|
||||
class="bg-red-600 hover:bg-red-700"
|
||||
@click="handleDelete"
|
||||
>
|
||||
<Button variant="destructive" class="bg-red-600 hover:bg-red-700" @click="handleRemove">
|
||||
<Check class="mr-1 h-4 w-4" />
|
||||
Ya
|
||||
</Button>
|
||||
|
||||
@@ -24,12 +24,7 @@ const ActiveForm = computed(() => formMap[type.value] || EarlyForm)
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<SoapiList
|
||||
v-if="mode === 'list'"
|
||||
@add="goToEntry"
|
||||
@edit="goToEntry"
|
||||
/>
|
||||
|
||||
<SoapiList v-if="mode === 'list'" />
|
||||
<component
|
||||
v-else
|
||||
:is="ActiveForm"
|
||||
|
||||
@@ -8,7 +8,8 @@ import { EarlySchema } from '~/schemas/soapi.schema'
|
||||
import { toast } from '~/components/pub/ui/toast'
|
||||
import { handleActionSave, handleActionEdit } from '~/handlers/soapi-early.handler'
|
||||
|
||||
const { backToList } = useQueryMode('mode')
|
||||
const { goToEntry, backToList } = useQueryCRUDMode('mode')
|
||||
const { recordId } = useQueryCRUDMode('record-id')
|
||||
const route = useRoute()
|
||||
const isOpenProcedure = ref(false)
|
||||
const isOpenDiagnose = ref(false)
|
||||
@@ -20,7 +21,7 @@ const schema = EarlySchema
|
||||
const payload = ref({
|
||||
encounter_id: 0,
|
||||
time: '',
|
||||
typeCode: 'early',
|
||||
typeCode: 'early-medic',
|
||||
value: '',
|
||||
})
|
||||
|
||||
@@ -46,7 +47,7 @@ const isLoading = reactive<DataTableLoader>({
|
||||
|
||||
async function getDiagnoses() {
|
||||
isLoading.isTableLoading = true
|
||||
const resp = await xfetch('/api/v1/diagnose-src')
|
||||
const resp = await xfetch(`/api/v1/soapi/${recordId}`)
|
||||
if (resp.success) {
|
||||
diagnoses.value = (resp.body as Record<string, any>).data
|
||||
}
|
||||
@@ -77,6 +78,7 @@ function handleOpen(type: string) {
|
||||
|
||||
const entryRef = ref()
|
||||
async function actionHandler(type: string) {
|
||||
console.log(type)
|
||||
if (type === 'back') {
|
||||
backToList()
|
||||
return
|
||||
|
||||
@@ -22,10 +22,14 @@ interface Props {
|
||||
encounter: Encounter
|
||||
label: string
|
||||
}
|
||||
const route = useRoute()
|
||||
const props = defineProps<Props>()
|
||||
const emits = defineEmits(['add', 'edit'])
|
||||
|
||||
const { recordId } = useQueryCRUDRecordId()
|
||||
const { goToEntry, backToList } = useQueryCRUDMode('mode')
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
const data = ref([])
|
||||
const encounterId = ref<number>(props?.encounter?.id || 0)
|
||||
const title = ref('')
|
||||
@@ -56,13 +60,10 @@ const hreaderPrep: HeaderPrep = {
|
||||
icon: 'i-lucide-users',
|
||||
addNav: {
|
||||
label: 'Tambah',
|
||||
onClick: () => emits('add'),
|
||||
onClick: () => goToEntry(),
|
||||
},
|
||||
}
|
||||
|
||||
const { recordId } = useQueryCRUDRecordId()
|
||||
const { goToEntry, backToList } = useQueryCRUDMode('mode')
|
||||
|
||||
const type = computed(() => (route.query.tab as string) || 'early-medical-assessment')
|
||||
|
||||
onMounted(async () => {
|
||||
@@ -70,13 +71,24 @@ onMounted(async () => {
|
||||
})
|
||||
|
||||
async function getMyList() {
|
||||
const url = `/api/v1/soapi?type-code=${typeCode.value}?includes=encounter`
|
||||
const url = `/api/v1/soapi?typeCode=${typeCode.value}&includes=encounter,employee&encounter-id=${route.params.id}`
|
||||
const resp = await xfetch(url)
|
||||
if (resp.success) {
|
||||
data.value = (resp.body as Record<string, any>).data
|
||||
}
|
||||
}
|
||||
|
||||
const goEdit = (id: string) => {
|
||||
router.replace({
|
||||
path: route.path,
|
||||
query: {
|
||||
...route.query,
|
||||
mode: 'entry',
|
||||
'record-id': id,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function handlePageChange(page: number) {
|
||||
emits('pageChange', page)
|
||||
}
|
||||
@@ -142,6 +154,14 @@ watch([recId, recAction], () => {
|
||||
}
|
||||
})
|
||||
|
||||
// watch(recId, () => {
|
||||
// console.log('recId', recId.value)
|
||||
// console.log('recIdaacin', recAction.value)
|
||||
// if (recAction.value === 'showEdit') {
|
||||
// goEdit(recId.value)
|
||||
// }
|
||||
// })
|
||||
|
||||
provide('rec_id', recId)
|
||||
provide('rec_action', recAction)
|
||||
provide('rec_item', recItem)
|
||||
|
||||
Reference in New Issue
Block a user