Merge branch 'dev' of https://github.com/dikstub-rssa/simrs-fe into feat/rm-rajal-183

This commit is contained in:
Abizrh
2025-12-06 09:31:18 +07:00
226 changed files with 9442 additions and 1162 deletions

No files matched your search

+66 -40
View File
@@ -3,14 +3,14 @@
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'
import AppViewHistory from '~/components/app/sep/view-history.vue'
// Helpers
import { refDebounced } from '@vueuse/core'
// Handlers
import { getDetail as getDoctorDetail } from '~/services/doctor.service'
import { useEncounterEntry } from '~/handlers/encounter-entry.handler'
import { genDoctor, type Doctor } from '~/models/doctor'
import { useIntegrationSepEntry } from '~/handlers/integration-sep-entry.handler'
// Props
const props = defineProps<{
@@ -34,54 +34,33 @@ const {
isLoadingDetail,
formObjects,
openPatient,
isMemberValid,
isSepValid,
isCheckingSep,
isSaveDisabled,
isSaving,
isLoading,
patients,
selectedDoctor,
selectedPatient,
selectedPatientObject,
paginationMeta,
toNavigateSep,
getListPath,
handleInit,
loadEncounterDetail,
getFetchEncounterDetail,
handleSaveEncounter,
getPatientsList,
getPatientCurrent,
getPatientByIdentifierSearch,
getIsSubspecialist,
// getIsSubspecialist,
getDoctorInfo,
getValidateMember,
getValidateSepNumber,
handleFetchDoctors,
} = useEncounterEntry(props)
const { recSepId, openHistory, histories, getMonitoringHistoryMappers } = useIntegrationSepEntry()
const debouncedSepNumber = refDebounced(sepNumber, 500)
const selectedDoctor = ref<Doctor>(genDoctor())
provide('rec_select_id', recSelectId)
provide('table_data_loader', isLoading)
watch(debouncedSepNumber, async (newValue) => {
await getValidateSepNumber(newValue)
})
watch(
() => formObjects.value?.paymentType,
(newValue) => {
isSepValid.value = false
if (newValue !== 'jkn') {
sepNumber.value = ''
}
},
)
onMounted(async () => {
await handleInit()
if (props.id > 0) {
await loadEncounterDetail()
}
})
///// Functions
function handleSavePatient() {
@@ -116,12 +95,28 @@ async function handleEvent(menu: string, value?: any) {
}
toNavigateSep({
isService: 'false',
encounterId: props.id || null,
sourcePath: route.path,
resource: `${props.classCode}-${props.subClassCode}`,
...value,
})
} else if (menu === 'sep-number-changed') {
await getValidateSepNumber(String(value || ''))
const sepNumberText = String(value || '').trim()
if (sepNumberText.length > 5) {
await getValidateSepNumber(sepNumberText)
}
} else if (menu === 'member-changed') {
const memberText = String(value || '').trim()
if (memberText.length > 5) {
await getValidateMember(memberText)
}
} else if (menu === 'search-sep') {
const memberText = String(value?.cardNumber || '').trim()
if (memberText.length < 5) return
getMonitoringHistoryMappers(memberText).then(() => {
openHistory.value = true
})
return
} else if (menu === 'save') {
await handleSaveEncounter(value)
} else if (menu === 'cancel') {
@@ -129,13 +124,39 @@ async function handleEvent(menu: string, value?: any) {
}
}
async function getDoctorInfo(value: string) {
const resp = await getDoctorDetail(value, { includes: 'unit,specialist,subspecialist'})
if (resp.success) {
selectedDoctor.value = resp.body.data
// console.log(selectedDoctor.value)
provide('rec_select_id', recSelectId)
provide('rec_sep_id', recSepId)
provide('table_data_loader', isLoading)
watch(debouncedSepNumber, async (newValue) => {
await getValidateSepNumber(newValue)
})
watch(
() => formObjects.value?.paymentType,
(newValue) => {
isSepValid.value = false
if (newValue !== 'jkn') {
sepNumber.value = ''
}
},
)
watch(
() => props.id,
async (newId) => {
if (props.formType === 'edit' && newId > 0) {
await getFetchEncounterDetail()
}
},
)
onMounted(async () => {
await handleInit()
if (props.formType === 'edit' && props.id > 0) {
await getFetchEncounterDetail()
}
}
})
</script>
<template>
@@ -144,13 +165,15 @@ async function getDoctorInfo(value: string) {
name="i-lucide-user"
class="me-2"
/>
<span class="font-semibold">{{ props.formType }}</span>
<span class="font-semibold">{{ props.formType === 'add' ? 'Tambah' : 'Ubah' }}</span>
Kunjungan
</div>
<AppEncounterEntryForm
ref="formRef"
:mode="props.formType"
:is-loading="isLoadingDetail"
:is-member-valid="isMemberValid"
:is-sep-valid="isSepValid"
:is-checking-sep="isCheckingSep"
:payments="paymentsList"
@@ -165,7 +188,6 @@ async function getDoctorInfo(value: string) {
@event="handleEvent"
@fetch="handleFetch"
/>
<AppViewPatient
v-model:open="openPatient"
v-model:selected="selectedPatient"
@@ -184,7 +206,11 @@ async function getDoctorInfo(value: string) {
"
@save="handleSavePatient"
/>
<AppViewHistory
v-model:open="openHistory"
:is-action="true"
:histories="histories"
/>
<!-- Footer Actions -->
<div class="mt-6 flex justify-end gap-2 border-t border-t-slate-300 pt-4">
<Button
+56 -32
View File
@@ -13,7 +13,11 @@ import { useSidebar } from '~/components/pub/ui/sidebar/utils'
import { getServicePosition } from '~/lib/roles' // previously getPositionAs
// Services
import { getList as getEncounterList, remove as removeEncounter, cancel as cancelEncounter } from '~/services/encounter.service'
import {
getList as getEncounterList,
remove as removeEncounter,
cancel as cancelEncounter,
} from '~/services/encounter.service'
// Apps
import Content from '~/components/app/encounter/list.vue'
@@ -34,8 +38,14 @@ const props = defineProps<{
const { setOpen } = useSidebar()
setOpen(true)
// Role reactivities
const { getActiveRole } = useUserStore()
// Main data
const data = ref([])
const dataFiltered = ref([])
const filterParams = ref<any>({})
const activeServicePosition = ref(getServicePosition(getActiveRole()))
const isLoading = reactive<DataTableLoader>({
summary: false,
isTableLoading: false,
@@ -87,39 +97,38 @@ const filter = ref<{
schema: {},
})
// Role reactivities
const { getActiveRole } = useUserStore()
const activeServicePosition = ref(getServicePosition(getActiveRole()))
provide('activeServicePosition', activeServicePosition)
watch(getActiveRole, (role? : string) => {
activeServicePosition.value = getServicePosition(role)
})
// Recrod reactivities
provide('activeServicePosition', activeServicePosition)
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
watch(getActiveRole, (role?: string) => {
activeServicePosition.value = getServicePosition(role)
})
watch(
() => recAction.value,
() => {
const basePath = `/${props.classCode}/encounter`
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}/detail`)
} 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()
})
@@ -127,17 +136,21 @@ onMounted(() => {
/////// Functions
async function getPatientList() {
isLoading.isTableLoading = true
const includesParams =
'patient,patient-person,patient-person-addresses,unit,Appointment_Doctor,Appointment_Doctor-employee,Appointment_Doctor-employee-person,Responsible_Doctor,Responsible_Doctor-employee,Responsible_Doctor-employee-person'
data.value = []
try {
const params: any = { includes: 'patient,patient-person' }
const params: any = { includes: includesParams, ...filterParams.value }
if (props.classCode) {
params['class-code'] = props.classCode
params.class_code = props.classCode
}
if (props.subClassCode) {
params['sub-class-code'] = props.subClassCode
params.sub_class_code = props.subClassCode
}
const result = await getEncounterList(params)
if (result.success) {
data.value = result.body?.data || []
dataFiltered.value = [...data.value]
}
} catch (error) {
console.error('Error fetching encounter list:', error)
@@ -146,6 +159,15 @@ async function getPatientList() {
}
}
function handleFilterApply(filters: { personName: string; startDate: string; endDate: string }) {
filterParams.value = {
'person-name': filters.personName,
'start-date': filters.startDate,
'end-date': filters.endDate,
}
getPatientList()
}
// Handle confirmation result
async function handleConfirmCancel(record: any, action: string) {
if (action === 'deactivate' && record?.id) {
@@ -240,7 +262,8 @@ function handleRemoveConfirmation() {
<template>
<CH.ContentHeader v-bind="hreaderPrep">
<FilterNav
@onFilterClick="() => isFilterFormDialogOpen = true"
:active-positon="activeServicePosition"
@apply="handleFilterApply"
@onExportPdf="() => {}"
@onExportExcel="() => {}"
@nExportCsv="() => {}"
@@ -256,11 +279,12 @@ function handleRemoveConfirmation() {
size="lg"
prevent-outside
>
<FilterForm v-bind="filter" />
<FilterForm v-bind="filter" />
</Dialog>
<!-- Batal -->
<RecordConfirmation
v-if="canDelete"
v-model:open="isRecordCancelOpen"
custom-title="Batalkan Kunjungan"
custom-message="Apakah anda yakin ingin membatalkan kunjungan pasien berikut?"
@@ -26,6 +26,7 @@ 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'
import VaccineDataList from '~/components/content/vaccine-data/list.vue'
const route = useRoute()
const router = useRouter()
@@ -96,6 +97,7 @@ const tabs: TabItem[] = [
component: DocUploadList,
props: { encounter: data },
},
{ value: 'vaccine-data', label: 'Data Vaksin', component: VaccineDataList, props: { encounter: data } },
]
</script>
+23 -21
View File
@@ -34,6 +34,7 @@ import GeneralConsentList from '~/components/content/general-consent/entry.vue'
import SummaryMedic from '~/components/content/summary-medic/entry.vue'
import ResumeList from '~/components/content/resume/list.vue'
import ControlLetterList from '~/components/content/control-letter/list.vue'
import InitialNursingStudy from '~/components/content/initial-nursing/entry.vue'
// App Components
import EncounterPatientInfo from '~/components/app/encounter/quick-info.vue'
import EncounterHistoryButtonMenu from '~/components/app/encounter/quick-shortcut.vue'
@@ -43,6 +44,10 @@ import EncounterHistoryButtonMenu from '~/components/app/encounter/quick-shortcu
const props = defineProps<{
classCode: EncounterProps['classCode']
subClassCode?: EncounterProps['subClassCode']
canCreate?: boolean
canRead?: boolean
canUpdate?: boolean
canDelete?: boolean
}>()
// Common preparations
@@ -104,6 +109,13 @@ const protocolRows = [
{ 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: 'consent', label: 'General Consent', component: GeneralConsentList, props: { encounter: data } },
{
value: 'initial-nursing-study',
label: 'Kajian Awal Keperawatan',
component: InitialNursingStudy,
props: { encounter: data },
},
{ value: 'mcu-lab-micro', label: 'Order Lab Mikro' },
{ value: 'mcu-lab-pa', label: 'Order Lab PA' },
{ value: 'medical-action', label: 'Order Ruang Tindakan' },
@@ -149,7 +161,7 @@ onMounted(async () => {
///// Functions
function handleClick(type: string) {
if (type === 'draft') {
router.back()
router.push(`/${props.classCode}/encounter`)
}
}
@@ -176,27 +188,13 @@ async function getData() {
<template>
<div class="w-full">
<div class="bg-white p-4 dark:bg-slate-800 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 class="bg-white dark:bg-slate-800">
<div class="p-3 2xl:p-4 border-b border-slate-300 dark:border-slate-700">
<ContentNavBa label="Kembali Ke Daftar" @click="handleClick" />
</div>
<ContentSwitcher
:active="1"
:height="150"
>
<ContentSwitcher :active="1" class="h-[130px] 2xl:h-[160px]">
<template v-slot:content1>
<EncounterPatientInfo
v-if="isShowPatient"
:data="data"
/>
<EncounterPatientInfo v-if="isShowPatient" :data="data" />
</template>
<template v-slot:content2>
<EncounterHistoryButtonMenu v-if="isShowPatient" />
@@ -207,6 +205,10 @@ async function getData() {
:data="menus"
:initial-active-menu="activeMenu"
@change-menu="activeMenu = $event"
:can-create="canCreate"
:can-read="canRead"
:can-update="canUpdate"
:can-delete="canDelete"
/>
</div>
</template>
</template>
+19 -11
View File
@@ -5,6 +5,7 @@ import { getValueLabelList as getEmployeeValueLabelList } from '~/services/emplo
import { getValueLabelList as getUnitValueLabelList } from '~/services/unit.service'
import type { CheckInFormData, CheckOutFormData } from '~/schemas/encounter.schema'
import { CheckInSchema, CheckOutSchema } from '~/schemas/encounter.schema'
import { getEncounterData } from '~/handlers/encounter-process.handler'
//
import Dialog from '~/components/pub/my-ui/modal/dialog.vue'
@@ -18,10 +19,12 @@ import { checkIn } from '~/services/encounter.service'
//
const props = defineProps<{
encounter: Encounter
canUpdate?: boolean
}>()
// doctors
const doctors = await getDoctorValueLabelList({'includes': 'employee,employee-person'})
const localEncounter = ref<Encounter>(props.encounter)
const doctors = await getDoctorValueLabelList({'includes': 'employee,employee-person'}, true)
const employees = await getEmployeeValueLabelList({'includes': 'person', 'position-code': 'reg'})
const units = await getUnitValueLabelList()
@@ -32,7 +35,7 @@ const checkInValues = ref<any>({
// registeredAt: '',
})
const checkInIsLoading = ref(false)
const checkInIsReadonly = ref(false)
const checkInIsReadonly = ref(props.canUpdate )
const checkInDialogOpen = ref(false)
// check out
@@ -49,8 +52,15 @@ function editCheckIn() {
checkInDialogOpen.value = true
}
function submitCheckIn(values: CheckInFormData) {
checkIn(props.encounter.id, values)
async function submitCheckIn(values: CheckInFormData) {
const res = await checkIn(props.encounter.id, values)
if (res.success) {
checkInDialogOpen.value = false
const resEnc = await getEncounterData(props.encounter.id)
if (resEnc.success) {
localEncounter.value = resEnc.body.data
}
}
}
function editCheckOut() {
@@ -67,9 +77,8 @@ function submitCheckOut(values: CheckOutFormData) {
<div class="border-r lg:pe-4 xl:pe-5 mb-10">
<div class="mb-4 xl:mb-5 text-base text-center font-semibold">Informasi Masuk</div>
<CheckInView
:encounter="encounter"
:is-loading="checkInIsLoading"
:is-readonly="checkInIsReadonly"
:encounter="localEncounter"
:can-update="canUpdate"
@edit="editCheckIn"
/>
</div>
@@ -77,9 +86,8 @@ function submitCheckOut(values: CheckOutFormData) {
<Separator class="lg:hidden my-4 xl:my-5" />
<div class="mb-4 xl:mb-5 text-base text-center font-semibold">Informasi Keluar</div>
<CheckOutView
:encounter="encounter"
:is-loading="checkOutIsLoading"
:is-readonly="checkOutIsReadonly"
:encounter="localEncounter"
:can-update="canUpdate"
@edit="editCheckOut"
/>
</div>
@@ -122,4 +130,4 @@ function submitCheckOut(values: CheckOutFormData) {
@cancel="checkInDialogOpen = false"
/>
</Dialog>
</template>
</template>