Squashed commit of the following:

commit 9f5ede3aea
Merge: 16858dc dbe8e34
Author: Munawwirul Jamal <57973347+munaja@users.noreply.github.com>
Date:   Fri Dec 5 09:26:33 2025 +0700

    Merge pull request #213 from dikstub-rssa/feat/things-munaja

    Feat/things munaja

commit dbe8e34d07
Author: Munawwirul Jamal <munawwirul.jamal@gmail.com>
Date:   Fri Dec 5 09:26:00 2025 +0700

    feat/things-munaja: fix enc status

commit 786107ddd9
Author: Munawwirul Jamal <munawwirul.jamal@gmail.com>
Date:   Thu Dec 4 22:57:33 2025 +0700

    feat/things-munaja: content switcher

commit 4450cb943e
Merge: 9136093 16858dc
Author: Munawwirul Jamal <munawwirul.jamal@gmail.com>
Date:   Thu Dec 4 19:31:30 2025 +0700

    Merge branch 'dev' into feat/things-munaja

commit 9136093fd6
Author: Munawwirul Jamal <munawwirul.jamal@gmail.com>
Date:   Thu Dec 4 18:02:14 2025 +0700

    feat/things-munaja: cleaning

commit 16858dc2a0
Merge: 82e33c6 67a5154
Author: Munawwirul Jamal <57973347+munaja@users.noreply.github.com>
Date:   Thu Dec 4 14:30:35 2025 +0700

    Merge pull request #212 from dikstub-rssa/feat/encounter-adjustment-163

    Enhancement SEP

commit 67a515478c
Author: riefive <rie.five@gmail.com>
Date:   Thu Dec 4 13:39:36 2025 +0700

    fix: save sep

commit 82e33c6dbf
Merge: f96cbdb eba740a
Author: Munawwirul Jamal <57973347+munaja@users.noreply.github.com>
Date:   Thu Dec 4 13:21:37 2025 +0700

    Merge pull request #209 from dikstub-rssa/feat/move-kai-ui-to-sidebar-195

    Feat/move kai UI to sidebar 195

commit e6b74a4949
Author: riefive <rie.five@gmail.com>
Date:   Thu Dec 4 12:36:53 2025 +0700

    fix: encounter update form

commit 434f8471ce
Author: riefive <rie.five@gmail.com>
Date:   Thu Dec 4 12:20:07 2025 +0700

    fix: update save again

commit bae087aa6d
Author: riefive <rie.five@gmail.com>
Date:   Thu Dec 4 12:19:37 2025 +0700

    fix: update save

commit f96cbdb173
Merge: 5de0057 62556a5
Author: Munawwirul Jamal <57973347+munaja@users.noreply.github.com>
Date:   Thu Dec 4 12:07:25 2025 +0700

    Merge pull request #210 from dikstub-rssa/feat/procedure-room-order

    Feat/procedure room order

commit 62556a5c7d
Merge: 93a294d 5de0057
Author: Munawwirul Jamal <57973347+munaja@users.noreply.github.com>
Date:   Thu Dec 4 12:07:18 2025 +0700

    Merge branch 'dev' into feat/procedure-room-order

commit 5da439720f
Author: riefive <rie.five@gmail.com>
Date:   Thu Dec 4 11:59:37 2025 +0700

    feat: implement encounter entry form for patient, doctor, payment, and SEP management

commit 5de0057278
Merge: 71ca7f9 782034c
Author: Andsky <andrianovsky95@gmail.com>
Date:   Thu Dec 4 11:56:31 2025 +0700

    Merge pull request #211 from dikstub-rssa/feat/micro-lab-order-50

    Feat/micro lab order 50

commit 93a294d8d0
Merge: 6c26367 26365bb
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Thu Dec 4 11:18:49 2025 +0700

    Merge branch 'dev' into feat/procedure-room-order

commit 26365bbd2e
Merge: b6b56d1 e7cab6c
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Thu Dec 4 11:18:33 2025 +0700

    Merge branch 'dev' of github.com:dikstub-rssa/simrs-fe into dev

commit 6c26367c1d
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Thu Dec 4 10:51:40 2025 +0700

    feat/procedure-room-order: added item

commit d2ceda37bf
Author: riefive <rie.five@gmail.com>
Date:   Thu Dec 4 10:58:16 2025 +0700

    feat: Implement encounter list management with search, date range filtering, and record actions.

commit a0dfd214e5
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Thu Dec 4 10:25:50 2025 +0700

    feat/procedure-room-order: finishing

commit acb573e279
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Thu Dec 4 10:25:35 2025 +0700

    feat/procedure-room-order: adjust procedure-room-order

commit 9adb77d10b
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Thu Dec 4 10:24:52 2025 +0700

    feat/procedure-room-order: adjust material-package

commit 52454a019e
Merge: 0a0fb73 b6b56d1
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Thu Dec 4 10:23:14 2025 +0700

    Merge branch 'dev' into feat/procedure-room-order

commit b6b56d186d
Author: Munawwirul Jamal <munawwirul.jamal@gmail.com>
Date:   Thu Dec 4 10:17:16 2025 +0700

    dev: hotfix, cleaning some pubs

commit acc45b205f
Author: riefive <rie.five@gmail.com>
Date:   Wed Dec 3 16:25:15 2025 +0700

    fix: upload sep + sipp file

commit 0a0fb73483
Author: Munawwirul Jamal <munawwirul.jamal@gmail.com>
Date:   Wed Dec 3 14:33:13 2025 +0700

    feat/procedure-room-order: flow for procedure room

commit 7b4b4eecf9
Merge: 9f72e1d b172125
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Wed Dec 3 10:18:15 2025 +0700

    Merge branch 'feat/mcu-order' into feat/procedure-room-order

commit 782034c2b1
Merge: bae0a22 b80ee5a
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Wed Dec 3 10:16:45 2025 +0700

    Merge branch 'dev' into feat/micro-lab-order-50

commit 9f72e1df47
Merge: fa0ae98 b80ee5a
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Wed Dec 3 10:02:57 2025 +0700

    Merge branch 'dev' into feat/procedure-room-order

commit fa0ae9866e
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Wed Dec 3 09:44:33 2025 +0700

    feat/procedure-room-order: wip #3

commit ce93f996d9
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Wed Dec 3 09:43:36 2025 +0700

    feat/procedure-room-order: wip #2
    + procedure-room

commit 4444e87cb3
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Wed Dec 3 09:42:31 2025 +0700

    feat/procedure-room-order: wip #1

    + material-package
    + material-package-item

commit b172125d99
Merge: b2d3c14 9b7a719
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Mon Dec 1 20:41:58 2025 +0700

    Merge branch 'feat/page-cleaning' into feat/mcu-order

commit bae0a222b8
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Mon Dec 1 01:51:04 2025 +0700

    feat/micro-lab-order-50: adjust for antibiotic

commit 1ee0f39e7d
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Mon Dec 1 01:50:39 2025 +0700

    feat/micro-lab-order-50: added antibiotic

commit 95e27a8b6f
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Sun Nov 30 13:00:04 2025 +0700

    feat/micro-lab-order-50: wip

commit a002ef6c6e
Merge: fb2f01b b2d3c14
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Sun Nov 30 12:50:31 2025 +0700

    Merge branch 'feat/mcu-order' into feat/micro-lab-order-50

commit b2d3c14ddc
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Sun Nov 30 12:48:11 2025 +0700

    feat/mcu-order: adjustment

commit fb2f01bd36
Merge: 9a481fe 5c92f8b
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Sun Nov 30 12:43:11 2025 +0700

    Merge branch 'feat/mcu-order' into feat/micro-lab-order-50

commit 5c92f8b946
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Sun Nov 30 12:30:19 2025 +0700

    feat/mcu-order: added the components

commit 9a481fec14
Merge: 4251239 78fc289
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Fri Nov 28 21:18:10 2025 +0700

    Merge branch 'dev' into feat/micro-lab-order-50

commit 4251239f7c
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Fri Nov 28 21:17:18 2025 +0700

    feat/micro-lab-order-50: wip

commit cf5789549e
Author: Munawwirul Jamal <munawwirul.jamal@gmail.com>
Date:   Sun Nov 23 15:56:31 2025 +0700

    feat/mcu: improved wip

commit a40eac35f8
Merge: 3211972 baf6ab1
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Wed Nov 19 22:23:18 2025 +0700

    Merge branch 'dev' into feat/radiology-order-54

commit 3211972a84
Merge: e3fc1e4 ccabe01
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Sat Nov 15 20:15:07 2025 +0700

    Merge branch 'dev' into feat/radiology-order-54

commit e3fc1e4ab9
Author: Andrian Roshandy <andrianovsky95@gmail.com>
Date:   Sat Nov 15 20:14:38 2025 +0700

    feat/radiology-order-54: adjust wip
This commit is contained in:
Hasyim Kai
2025-12-05 14:16:05 +07:00
parent 3e812db8eb
commit 75b9618a26
26 changed files with 571 additions and 333 deletions
+19 -12
View File
@@ -10,12 +10,13 @@ import ComboBox from '~/components/pub/my-ui/combobox/combobox.vue'
import * as DE from '~/components/pub/my-ui/doc-entry'
import type { CheckInFormData } from '~/schemas/encounter.schema'
import type { Encounter } from '~/models/encounter'
import { now } from '@internationalized/date';
interface Props {
schema: z.ZodSchema<any>
values: any
doctors: { value: string; label: string }[]
employees: { value: string; label: string }[]
// employees: { value: string; label: string }[]
encounter: Encounter
isLoading?: boolean
isReadonly?: boolean
@@ -36,18 +37,23 @@ const { defineField, errors, meta } = useForm({
} as Partial<CheckInFormData>,
})
const [responsible_doctor_id, responsible_doctor_idAttrs] = defineField('responsible_doctor_id')
const [adm_employee_id, adm_employee_idAttrs] = defineField('discharge_method_code')
const [responsible_doctor_code, responsible_doctor_codeAttrs] = defineField('responsible_doctor_code')
// const [adm_employee_id, adm_employee_idAttrs] = defineField('discharge_method_code')
const [registeredAt, registeredAtAttrs] = defineField('registeredAt')
function submitForm() {
const formData: CheckInFormData = {
responsible_doctor_id: responsible_doctor_id.value,
adm_employee_id: adm_employee_id.value,
// registeredAt: registeredAt.value || '',
responsible_doctor_code: responsible_doctor_code.value,
// adm_employee_id: adm_employee_id.value,
registeredAt: registeredAt.value || '',
}
emit('submit', formData)
}
function setTime() {
const today = new Date()
registeredAt.value = today.toISOString().substring(0, 10) + ' ' + today.toLocaleTimeString('id-ID').substring(0, 5).replace('.', ':');
}
</script>
<template>
@@ -57,8 +63,8 @@ function submitForm() {
<DE.Field>
<ComboBox
id="doctor"
v-model="responsible_doctor_id"
v-bind="responsible_doctor_idAttrs"
v-model="responsible_doctor_code"
v-bind="responsible_doctor_codeAttrs"
:items="doctors"
:disabled="isLoading || isReadonly"
placeholder="Pilih Dokter DPJP"
@@ -67,7 +73,7 @@ function submitForm() {
/>
</DE.Field>
</DE.Cell>
<DE.Cell>
<!-- <DE.Cell>
<DE.Label>PJ Berkas</DE.Label>
<DE.Field>
<ComboBox
@@ -81,7 +87,7 @@ function submitForm() {
empty-message="Petugas tidak ditemukan"
/>
</DE.Field>
</DE.Cell>
</DE.Cell> -->
<DE.Cell>
<DE.Label>Waktu Masuk</DE.Label>
<DE.Field>
@@ -89,7 +95,8 @@ function submitForm() {
id="name"
v-model="registeredAt"
v-bind="registeredAtAttrs"
:disabled="isLoading || isReadonly"
@click="setTime"
readonly
/>
</DE.Field>
</DE.Cell>
@@ -104,4 +111,4 @@ function submitForm() {
<style>
</style>
</style>
+25 -20
View File
@@ -7,43 +7,48 @@ import type { Encounter } from '~/models/encounter'
interface Props {
encounter: Encounter
canUpdate?: boolean
isLoading?: boolean
}
const props = defineProps<Props>()
const doctor = ref('-belum dipilih-')
const doctor = computed(() => {
return props.encounter.responsible_doctor?.employee?.person?.name ?? '-belum dipilih-'
})
const adm = ref('-belum dipilih-')
const emit = defineEmits<{
edit: []
}>()
watch(props.encounter, () => {
doctor.value = props.encounter.responsible_doctor?.employee?.person?.name ?? props.encounter.appointment_doctor?.employee?.person?.name ?? '-belum dipilih-'
adm.value = props.encounter.adm_employee?.person?.name ?? '-belum dipilih-'
})
</script>
<template>
<DE.Block :cell-flex="false">
<DE.Cell>
<DE.Label class="font-semibold">Waktu Masuk</DE.Label>
<DE.Field>
<div class="py-2 border-b border-b-slate-300">{{ encounter?.registeredAt?.substring(0, 15).replace('T', ' ') || '-' }}</div>
</DE.Field>
</DE.Cell>
<DE.Cell>
<DE.Label class="font-semibold">PJ Berkas</DE.Label>
<DE.Field>
<div class="py-2 border-b border-b-slate-300">{{ encounter.adm_employee?.person?.name || '-' }}</div>
</DE.Field>
</DE.Cell>
<DE.Cell>
<DE.Label class="font-semibold">Perawat</DE.Label>
<DE.Field>
<div class="py-2 border-b border-b-slate-300">{{ encounter.responsible_nurse?.employee?.person?.name || '-' }}</div>
</DE.Field>
</DE.Cell>
<DE.Cell>
<DE.Label class="font-semibold">Dokter</DE.Label>
<DE.Field>
<div class="py-2 border-b border-b-slate-300">{{ doctor }}</div>
</DE.Field>
</DE.Cell>
<DE.Cell>
<DE.Label class="font-semibold">PJ Berkas</DE.Label>
<DE.Field>
<div class="py-2 border-b border-b-slate-300">{{ adm }}</div>
</DE.Field>
</DE.Cell>
<DE.Cell>
<DE.Label class="font-semibold">Waktu Masuk</DE.Label>
<DE.Field>
<div class="py-2 border-b border-b-slate-300">{{ encounter?.registeredAt || '-' }}</div>
</DE.Field>
</DE.Cell>
</DE.Block>
<div class="text-center">
<div v-if="canUpdate" class="text-center">
<Button @click="() => emit('edit')">
<LucidePen />
Edit
@@ -53,4 +58,4 @@ watch(props.encounter, () => {
<style>
</style>
</style>
@@ -12,8 +12,7 @@ import type { Encounter } from '~/models/encounter';
interface Props {
encounter: Encounter
isLoading?: boolean
isReadonly?: boolean
canUpdate?: boolean
}
const props = defineProps<Props>()
@@ -73,7 +72,7 @@ const emit = defineEmits<{
</DE.Field>
</DE.Cell>
</DE.Block>
<div class="text-center [&>*]:mx-1">
<div v-if="canUpdate" class="text-center [&>*]:mx-1">
<Button @click="() => emit('edit')">
<LucidePen />
Edit
@@ -87,4 +86,4 @@ const emit = defineEmits<{
<style>
</style>
</style>
+56 -22
View File
@@ -23,6 +23,7 @@ import { paymentMethodCodes } from '~/const/key-val/common'
// App things
import { genEncounter, type Encounter } from '~/models/encounter'
import { se } from 'date-fns/locale'
// Props
const props = defineProps<{
@@ -48,7 +49,7 @@ const model = defineModel<Encounter>()
model.value = genEncounter()
// Common preparation
const defaultCBItems = [{ label: 'Pilih', value: '' }];
const defaultCBItems = [{ label: 'Pilih', value: '' }]
const paymentMethodItems = CB.recStrToItem(paymentMethodCodes)
// Emit preparation
@@ -95,7 +96,10 @@ const isInsurancePayment = computed(() => ['insurance', 'jkn'].includes(paymentM
const isDateLoading = ref(false)
const debouncedSepNumber = refDebounced(sepNumber, 500)
const debouncedCardNumber = refDebounced(cardNumber, 500)
const sepFileReview = ref<any>(null)
const sippFileReview = ref<any>(null)
const unitFullName = ref('') // Unit, specialist, subspecialist
const formRef = ref<HTMLFormElement | null>(null) // Expose submit method for parent component
if (mode === 'add') {
// Set default sepDate to current date in YYYY-MM-DD format
@@ -106,11 +110,10 @@ if (mode === 'add') {
registerDate.value = `${year}-${month}-${day}`
}
watch(() => props.selectedDoctor, (doctor) => {
unitFullName.value = doctor.subspecialist?.name ??
doctor.specialist?.name ??
doctor.unit?.name ??
'tidak diketahui'
watch(
() => props.selectedDoctor,
(doctor) => {
unitFullName.value = doctor.subspecialist?.name ?? doctor.specialist?.name ?? doctor.unit?.name ?? 'tidak diketahui'
model.value!.unit_code = doctor.unit_code || ''
model.value!.specialist_code = doctor.specialist_code || ''
model.value!.subspecialist_code = doctor.subspecialist_code || ''
@@ -126,12 +129,13 @@ watch(
nationalIdentity.value = objects?.nationalIdentity || ''
medicalRecordNumber.value = objects?.medicalRecordNumber || ''
doctorCode.value = objects?.doctorCode || ''
// subSpecialistCode.value = objects?.subSpecialistCode || ''
paymentMethodCode.value = objects?.paymentMethodCode || ''
patientCategory.value = objects?.patientCategory || ''
cardNumber.value = objects?.cardNumber || ''
sepType.value = objects?.sepType || ''
sepNumber.value = objects?.sepNumber || ''
sepFileReview.value = objects?.sepFileReview || ''
sippFileReview.value = objects?.sippFileReview || ''
isDateLoading.value = true
setTimeout(() => {
registerDate.value = objects?.registerDate || ''
@@ -181,11 +185,12 @@ function onAddSep() {
const formValues = {
patientId: patientId.value || '',
doctorCode: doctorCode.value,
// subSpecialistCode: subSpecialistCode.value,
registerDate: registerDate.value,
cardNumber: cardNumber.value,
paymentMethodCode: paymentMethodCode.value,
sepType: sepType.value
sepFile: sepFile.value,
sippFile: sippFile.value,
sepType: sepType.value,
}
emit('event', 'add-sep', formValues)
}
@@ -196,12 +201,20 @@ function onSearchSep() {
// Submit handler
const onSubmit = handleSubmit((values) => {
console.log('✅ Validated form values:', JSON.stringify(values, null, 2))
emit('event', 'save', values)
let payload: any = values
if (props.mode === 'edit') {
payload = {
...payload,
sepFileReview: sepFileReview.value,
sippFileReview: sippFileReview.value,
}
}
emit('event', 'save', payload)
})
// Expose submit method for parent component
const formRef = ref<HTMLFormElement | null>(null)
function openFile(path: string) {
window.open(path, '_blank')
}
function submitForm() {
// Trigger form submit using native form submit
@@ -346,7 +359,10 @@ defineExpose({
<span class="text-red-500">*</span>
</DE.Label>
<DE.Field :errMessage="errors.unit_code">
<Input :value="unitFullName" :disabled="true"/>
<Input
:value="unitFullName"
:disabled="true"
/>
</DE.Field>
</DE.Cell>
@@ -414,7 +430,7 @@ defineExpose({
placeholder="Pilih Kelompok Peserta"
/>
</DE.Field>
<span class="text-sm text-gray-500">
<span class="text-sm text-gray-500">
{{ noteReference }}
</span>
</DE.Cell>
@@ -433,7 +449,7 @@ defineExpose({
placeholder="Masukkan nomor kartu BPJS"
/>
</DE.Field>
<div
<div
v-if="isMemberValid"
class="mt-1 flex items-center gap-2"
>
@@ -553,11 +569,20 @@ defineExpose({
:max-size-mb="1"
v-model="sepFile"
v-bind="sepFileAttrs"
@file-selected="(file: any) => { console.log(file) }"
@file-selected="() => {}"
/>
<span class="mt-1 text-sm text-gray-500">
{{ noteFile }}
</span>
<p v-if="sepFileReview">
<a
class="mt-1 text-sm text-blue-500 capitalize"
href="#"
@click="openFile(sepFileReview.filePath)"
>
{{ sepFileReview?.fileName }}
</a>
</p>
</DE.Cell>
<DE.Cell>
@@ -569,11 +594,20 @@ defineExpose({
:max-size-mb="1"
v-model="sippFile"
v-bind="sippFileAttrs"
@file-selected="(file: any) => { console.log(file) }"
@file-selected="() => {}"
/>
<span class="mt-1 text-sm text-gray-500">
{{ noteFile }}
</span>
<p v-if="sippFileReview">
<a
class="mt-1 text-sm text-blue-500 capitalize"
href="#"
@click="openFile(sippFileReview.filePath)"
>
{{ sippFileReview?.fileName }}
</a>
</p>
</DE.Cell>
</DE.Block>
</template>
@@ -591,13 +625,13 @@ defineExpose({
>
<DE.Cell>
<Label height="compact">Dengan Rujukan / Surat Kontrol</Label>
<Field>
<DE.Field>
<Input
id="sepReference"
v-model="sepReference"
:disabled="true"
/>
</Field>
</DE.Field>
</DE.Cell>
<DE.Cell>
@@ -635,13 +669,13 @@ defineExpose({
>
<DE.Cell :col-span="2">
<Label height="compact">Diagnosis</Label>
<Field>
<DE.Field>
<Input
id="diagnosis"
v-model="diagnosis"
:disabled="true"
/>
</Field>
</DE.Field>
</DE.Cell>
<DE.Cell>
+44 -36
View File
@@ -18,25 +18,13 @@ const props = defineProps<{
onExportCsv?: () => void
}>()
// function emitSearchNavClick() {
// props.refSearchNav?.onClick()
// }
//
// function onInput(event: Event) {
// props.refSearchNav?.onInput((event.target as HTMLInputElement).value)
// }
//
// function btnClick() {
// props.prep?.addNav?.onClick?.()
// }
const emit = defineEmits<{
apply: [filters: { personName: string; startDate: string; endDate: string }]
}>()
const searchQuery = ref('')
const isRoleRegistration = props.activePositon === 'registration'
const isRoleMedical = props.activePositon === 'medical'
const dateRange = ref<{ from: Date | null; to: Date | null }>({
from: new Date(),
to: new Date(),
})
const df = new DateFormatter('en-US', {
dateStyle: 'medium',
@@ -49,31 +37,47 @@ const todayCalendar = new CalendarDate(today.getFullYear(), today.getMonth() + 1
// Get date 1 month ago
const oneMonthAgo = new Date(today)
oneMonthAgo.setMonth(today.getMonth() - 1)
const oneMonthAgoCalendar = new CalendarDate(oneMonthAgo.getFullYear(), oneMonthAgo.getMonth() + 1, oneMonthAgo.getDate())
const oneMonthAgoCalendar = new CalendarDate(
oneMonthAgo.getFullYear(),
oneMonthAgo.getMonth() + 1,
oneMonthAgo.getDate(),
)
const value = ref({
start: oneMonthAgoCalendar,
end: todayCalendar,
}) as Ref<DateRange>
// function onFilterClick() {
// console.log('Search:', searchQuery.value)
// console.log('Date Range:', dateRange.value)
// props.refSearchNav?.onClick()
// }
function onFilterClick() {
const startDate = value.value.start ? value.value.start.toString() : ''
const endDate = value.value.end ? value.value.end.toString() : startDate
emit('apply', {
personName: searchQuery.value,
startDate,
endDate,
})
}
</script>
<template>
<div class="relative w-64">
<Search class="absolute left-3 top-1/2 size-4 -translate-y-1/2 text-gray-400" />
<Input v-model="searchQuery" type="text" placeholder="Cari Nama /No.RM" class="pl-9" />
<Input
v-model="searchQuery"
type="text"
placeholder="Cari Nama /No.RM"
class="pl-9"
/>
</div>
<Popover>
<PopoverTrigger as-child>
<Button
variant="outline"
:class="cn('min-w-[240px] max-w-[320px] justify-start text-left font-normal', !value && 'text-muted-foreground')"
:class="
cn('min-w-[240px] max-w-[320px] justify-start text-left font-normal', !value && 'text-muted-foreground')
"
>
<CalendarIcon class="mr-2 h-4 w-4" />
<template v-if="value.start">
@@ -86,7 +90,7 @@ const value = ref({
{{ df.format(value.start.toDate(getLocalTimeZone())) }}
</template>
</template>
<template v-else> Pick a date </template>
<template v-else>Pick a date</template>
</Button>
</PopoverTrigger>
<PopoverContent class="w-auto p-0">
@@ -99,28 +103,32 @@ const value = ref({
</PopoverContent>
</Popover>
<Button variant="outline" class="border-orange-500 text-orange-600 hover:bg-orange-50" @click="onFilterClick">
<Button
variant="outline"
class="border-orange-500 text-orange-600 hover:bg-orange-50"
@click="onFilterClick"
>
<FilterIcon class="mr-2 size-4" />
Filter
</Button>
<DropdownMenu v-show="props.enableExport && (isRoleRegistration || isRoleMedical)">
<DropdownMenuTrigger as-child>
<Button variant="outline" class="ml-auto border-orange-500 text-orange-600 hover:bg-orange-50">
<Icon name="i-lucide-download" class="h-4 w-4" />
<Button
variant="outline"
class="ml-auto border-orange-500 text-orange-600 hover:bg-orange-50"
>
<Icon
name="i-lucide-download"
class="h-4 w-4"
/>
Ekspor
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem @click="onExportPdf">
Ekspor PDF
</DropdownMenuItem>
<DropdownMenuItem @click="onExportCsv">
Ekspor CSV
</DropdownMenuItem>
<DropdownMenuItem @click="onExportExcel">
Ekspor Excel
</DropdownMenuItem>
<DropdownMenuItem @click="onExportPdf">Ekspor PDF</DropdownMenuItem>
<DropdownMenuItem @click="onExportCsv">Ekspor CSV</DropdownMenuItem>
<DropdownMenuItem @click="onExportExcel">Ekspor Excel</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</template>
+5 -5
View File
@@ -42,14 +42,14 @@ if (props.data.responsible_doctor) {
</script>
<template>
<div class="w-full">
<div class="w-full py-3 2xl:py-4 px-5">
<!-- Data Pasien -->
<h2 class="mb-2 font-semibold md:text-base 2xl:text-lg">
{{ data.patient.person.name }} - {{ data.patient.number }}
</h2>
<div class="grid grid-cols-3">
<div>
<DE.Block mode="preview">
<DE.Block mode="preview" class="!mb-0">
<DE.Cell>
<DE.Label class="font-semibold">Tgl. Lahir</DE.Label>
<DE.Colon />
@@ -58,7 +58,7 @@ if (props.data.responsible_doctor) {
</DE.Field>
</DE.Cell>
<DE.Cell>
<DE.Label class="font-semibold">Jns. Kelamin</DE.Label>
<DE.Label class="font-semibold"><span class="hidden xl:inline">Jns.</span> Kelamin</DE.Label>
<DE.Colon />
<DE.Field>
{{ genderCodes[data.patient.person.gender_code as keyof typeof genderCodes] }}
@@ -74,7 +74,7 @@ if (props.data.responsible_doctor) {
</DE.Block>
</div>
<div>
<DE.Block mode="preview">
<DE.Block mode="preview" class="!mb-0">
<DE.Cell>
<DE.Label position="dynamic" class="font-semibold">Tgl. Masuk</DE.Label>
<DE.Colon />
@@ -99,7 +99,7 @@ if (props.data.responsible_doctor) {
</DE.Block>
</div>
<div>
<DE.Block mode="preview">
<DE.Block mode="preview" class="!mb-0">
<DE.Cell>
<DE.Label position="dynamic" class="font-semibold">DPJP</DE.Label>
<DE.Colon />
+41 -39
View File
@@ -87,44 +87,46 @@ function handleClick(value: string) {
</script>
<template>
<h2 class="mb-4 font-semibold md:text-base 2xl:text-lg">Menu Cepat:</h2>
<div class="flex flex-wrap gap-2 mb-2">
<Button
v-for="item in itemsOne"
:key="item.value"
:class="[
'flex items-center gap-2 rounded-lg border px-3 py-1 text-sm font-medium transition-colors',
activeMenu === item.value
? 'border-orange-300 bg-orange-400 text-white'
: 'border-orange-300 bg-white text-orange-500 hover:bg-orange-400 hover:text-white'
]"
@click="handleClick(item.value)"
>
<Icon v-if="item.type === 'icon'"
:name="item.icon"
class="h-4 w-4"
/>
<img v-else-if="item.type === 'image'" :src="item.icon" class="h-[24px] w-[24px] object-contain" />
{{ item.label }}
</Button>
</div>
<div class="flex flex-wrap gap-2 mb-2">
<Button
v-for="item in itemsTwo"
:key="item.value"
:class="[
'flex items-center gap-2 rounded-lg border px-3 py-1 text-sm font-medium transition-colors',
activeMenu === item.value
? 'border-orange-300 bg-orange-400 text-white'
: 'border-orange-300 bg-white text-orange-500 hover:bg-orange-400 hover:text-white'
]"
@click="handleClick(item.value)"
>
<Icon
:name="item.icon"
class="h-4 w-4"
/>
{{ item.label }}
</Button>
<div class="w-full py-3 2xl:py-4 px-5">
<h2 class="mb-2 font-semibold md:text-base 2xl:text-lg">Akses Cepat:</h2>
<div class="flex flex-wrap gap-2 mb-2">
<Button
v-for="item in itemsOne"
:key="item.value"
:class="[
'flex items-center gap-2 rounded-lg border px-3 py-1 text-sm font-medium transition-colors',
activeMenu === item.value
? 'border-orange-300 bg-orange-400 text-white'
: 'border-orange-300 bg-white text-orange-500 hover:bg-orange-400 hover:text-white'
]"
@click="handleClick(item.value)"
>
<Icon v-if="item.type === 'icon'"
:name="item.icon"
class="h-4 w-4"
/>
<img v-else-if="item.type === 'image'" :src="item.icon" class="h-[24px] w-[24px] object-contain" />
{{ item.label }}
</Button>
</div>
<div class="flex flex-wrap gap-2 mb-2">
<Button
v-for="item in itemsTwo"
:key="item.value"
:class="[
'flex items-center gap-2 rounded-lg border px-3 py-1 text-sm font-medium transition-colors',
activeMenu === item.value
? 'border-orange-300 bg-orange-400 text-white'
: 'border-orange-300 bg-white text-orange-500 hover:bg-orange-400 hover:text-white'
]"
@click="handleClick(item.value)"
>
<Icon
:name="item.icon"
class="h-4 w-4"
/>
{{ item.label }}
</Button>
</div>
</div>
</template>
@@ -31,12 +31,6 @@ function navClick(type: 'cancel' | 'edit' | 'submit', data: Prescription): void
<template>
<div v-if="data.length == 0" class="p-10 text-center">
<div class="mb-4 xl:mb-5">Belum Ada Data</div>
<div>
<Button>
<Icon name="i-lucide-plus" class="me-2 align-middle" />
Tambah Order
</Button>
</div>
</div>
<Table.Table>
<Table.TableHeader>
@@ -32,12 +32,6 @@ function navClick(type: 'cancel' | 'edit' | 'submit', data: Prescription): void
<template>
<div v-if="data.length == 0" class="p-10 text-center">
<div class="mb-4 xl:mb-5">Belum Ada Data</div>
<div>
<Button>
<Icon name="i-lucide-plus" class="me-2 align-middle" />
Tambah Order
</Button>
</div>
</div>
<template v-for="item, idx in data">
<div :class="'text-sm 2xl:text-base font-semibold ' + (item.status_code == 'new' ? 'mb-2' : 'mb-2')">
-1
View File
@@ -142,7 +142,6 @@ const onSaveNumber = () => {
// Submit handler
const onSubmit = handleSubmit((values) => {
console.log('✅ Validated form values:', JSON.stringify(values, null, 2))
emit('event', 'save-sep', values)
})
+44 -24
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'
@@ -40,6 +44,7 @@ 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,
@@ -99,27 +104,30 @@ provide('rec_action', recAction)
provide('rec_item', recItem)
provide('table_data_loader', isLoading)
watch(getActiveRole, (role? : string) => {
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
})
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()
@@ -128,13 +136,16 @@ 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) {
@@ -148,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) {
@@ -243,7 +263,7 @@ function handleRemoveConfirmation() {
<CH.ContentHeader v-bind="hreaderPrep">
<FilterNav
:active-positon="activeServicePosition"
@onFilterClick="() => isFilterFormDialogOpen = true"
@apply="handleFilterApply"
@onExportPdf="() => {}"
@onExportExcel="() => {}"
@nExportCsv="() => {}"
@@ -259,7 +279,7 @@ function handleRemoveConfirmation() {
size="lg"
prevent-outside
>
<FilterForm v-bind="filter" />
<FilterForm v-bind="filter" />
</Dialog>
<!-- Batal -->
+17 -23
View File
@@ -43,6 +43,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
@@ -107,8 +111,8 @@ const protocolRows = [
{ 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-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' },
@@ -154,7 +158,7 @@ onMounted(async () => {
///// Functions
function handleClick(type: string) {
if (type === 'draft') {
router.back()
router.push(`/${props.classCode}/encounter`)
}
}
@@ -181,27 +185,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" />
@@ -212,6 +202,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>
@@ -1,40 +1,33 @@
<script setup lang="ts">
const props = defineProps<{
height?: number
class?: string
activeTab?: 1 | 2
}>()
const classVal = computed(() => {
return props.class ? props.class : ''
})
const activeTab = ref(props.activeTab || 1)
function handleClick(value: 1 | 2) {
activeTab.value = value
function switchActiveTab() {
activeTab.value = activeTab.value === 1 ? 2 : 1
}
</script>
<template>
<div class="content-switcher" :style="`height: ${height || 200}px`">
<div :class="`${activeTab === 1 ? 'active' : 'inactive'}`">
<div class="content-wrapper">
<div>
<slot name="content1" />
</div>
<div :class="`content-switcher ${classVal}`" :style="height ? `height:${200}px` : ''">
<div class="wrapper">
<div :class="`item item-1 ${activeTab === 1 ? 'active' : 'inactive'}`">
<slot name="content1" />
</div>
<div class="content-nav">
<button @click="handleClick(1)">
<Icon name="i-lucide-chevron-right" />
<div :class="`nav border-slate-300 ${ activeTab == 1 ? 'border-l' : 'border-r'}`">
<button @click="switchActiveTab()" class="!p-0 w-full h-full">
<Icon :name="activeTab == 1 ? 'i-lucide-chevron-left' : 'i-lucide-chevron-right'" class="text-3xl" />
</button>
</div>
</div>
<div :class="`${activeTab === 2 ? 'active' : 'inactive'}`">
<div class="content-nav">
<button @click="handleClick(2)">
<Icon name="i-lucide-chevron-left" />
</button>
</div>
<div class="content-wrapper">
<div>
<slot name="content2" />
</div>
<div :class="`item item-2 ${activeTab === 2 ? 'active' : 'inactive'}`">
<slot name="content2" />
</div>
</div>
</div>
@@ -42,45 +35,24 @@ function handleClick(value: 1 | 2) {
<style>
.content-switcher {
@apply flex overflow-hidden gap-3
@apply overflow-hidden
}
.content-switcher > * {
@apply border border-slate-300 rounded-md flex overflow-hidden
.wrapper {
@apply flex w-[200%] h-full
}
.item {
@apply w-[calc(50%-60px)]
}
.content-wrapper {
@apply p-4 2xl:p-5 overflow-hidden grow
.item-1.active {
@apply ms-0 transition-all duration-500 ease-in-out
}
.inactive .content-wrapper {
@apply p-0 w-0
.item-1.inactive {
@apply -ms-[calc(50%-60px)] transition-all duration-500 ease-in-out
}
.content-nav {
@apply h-full flex flex-row items-center justify-center content-center !text-2xl overflow-hidden
.nav {
@apply h-full w-[60px] flex flex-row items-center justify-center content-center !text-2xl overflow-hidden
}
.content-nav button {
@apply pt-2 px-2 h-full w-full
}
/* .content-switcher .inactive > .content-wrapper {
@apply w-0 p-0 opacity-0 transition-all duration-500 ease-in-out
} */
.content-switcher .inactive {
@apply w-16 transition-all duration-500 ease-in-out
}
.content-switcher .inactive > .content-nav {
@apply w-full transition-all duration-100 ease-in-out
}
.content-switcher .active {
@apply grow transition-all duration-500 ease-in-out
}
.content-switcher .active > .content-nav {
@apply w-0 transition-all duration-100 ease-in-out
}
/* .content-switcher .active > .content-wrapper {
@apply w-full delay-1000 transition-all duration-1000 ease-in-out
} */
</style>
+2 -2
View File
@@ -53,8 +53,8 @@ const settingClass = computed(() => {
'[&_.cell]:2xl:flex',
][getBreakpointIdx(props.cellFlexPoint)]
cls += ' [&_.label]:flex-shrink-0 ' + [
'[&_.label]:md:w-12 [&_.label]:xl:w-20',
'[&_.label]:md:w-16 [&_.label]:xl:w-24',
'[&_.label]:md:w-16 [&_.label]:xl:w-20',
'[&_.label]:md:w-20 [&_.label]:xl:w-24',
'[&_.label]:md:w-24 [&_.label]:xl:w-32',
'[&_.label]:md:w-32 [&_.label]:xl:w-40',
'[&_.label]:md:w-44 [&_.label]:xl:w-52',
+10 -1
View File
@@ -4,6 +4,10 @@ import { type EncounterItem } from "~/handlers/encounter-init.handler";
const props = defineProps<{
initialActiveMenu: string
data: EncounterItem[]
canCreate?: boolean
canRead?: boolean
canUpdate?: boolean
canDelete?: boolean
}>()
const activeMenu = ref(props.initialActiveMenu)
@@ -38,7 +42,12 @@ function changeMenu(value: string) {
class="flex-1 rounded-md border bg-white p-4 shadow-sm dark:bg-neutral-950">
<component
:is="data.find((m) => m.id === activeMenu)?.component"
v-bind="data.find((m) => m.id === activeMenu)?.props" />
v-bind="data.find((m) => m.id === activeMenu)?.props"
:can-create="canCreate"
:can-read="canRead"
:can-update="canUpdate"
:can-delete="canDelete"
/>
</div>
</div>
</div>
+127 -7
View File
@@ -39,14 +39,134 @@ export const permissions: Record<string, Record<string, Permission[]>> = {
'emp|reg': ['C', 'R', 'U', 'D'],
},
'/ambulatory/encounter/[id]/process': {
'emp|doc': ['R', 'U'],
'emp|doc': ['R'],
'emp|nur': ['R', 'U'],
'emp|thr': ['R', 'U'],
'emp|miw': ['R', 'U'],
'emp|nut': ['R', 'U'],
'emp|pha': ['R', 'U'],
'emp|lab': ['R', 'U'],
'emp|rad': ['R', 'U'],
'emp|thr': ['R'],
'emp|miw': ['R'],
'emp|nut': ['R'],
'emp|pha': ['R'],
'emp|lab': ['R'],
'emp|rad': ['R'],
},
'/ambulatory/encounter/[id]/process?menu=status': {
'emp|doc': ['R'],
'emp|nur': ['R', 'U'],
'emp|thr': ['R'],
'emp|miw': ['R'],
'emp|nut': ['R'],
'emp|pha': ['R'],
'emp|lab': ['R'],
'emp|rad': ['R'],
},
'/ambulatory/encounter/[id]/process?menu=early-medical-assessment': {
'emp|doc': ['R', 'U'],
'emp|nur': ['R'],
'emp|thr': ['R'],
'emp|miw': ['R'],
'emp|nut': ['R'],
'emp|pha': ['R'],
'emp|lab': ['R'],
'emp|rad': ['R'],
},
'/ambulatory/encounter/[id]/process?menu=fkr': {
'emp|doc': ['R', 'U'],
'emp|nur': ['R'],
'emp|thr': ['R'],
'emp|miw': ['R'],
'emp|nut': ['R'],
'emp|pha': ['R'],
'emp|lab': ['R'],
'emp|rad': ['R'],
},
'/ambulatory/encounter/[id]/process?menu=education-assessment': {
'emp|doc': ['R', 'U'],
'emp|nur': ['R'],
'emp|thr': ['R'],
'emp|miw': ['R'],
'emp|nut': ['R'],
'emp|pha': ['R'],
'emp|lab': ['R'],
'emp|rad': ['R'],
},
'/ambulatory/encounter/[id]/process?menu=general-consent': {
'emp|doc': ['R'],
'emp|nur': ['R', 'U'],
'emp|thr': ['R'],
'emp|miw': ['R'],
'emp|nut': ['R'],
'emp|pha': ['R'],
'emp|lab': ['R'],
'emp|rad': ['R'],
},
'/ambulatory/encounter/[id]/process?menu=patient-amb-note': {
'emp|doc': ['R'],
'emp|nur': ['R', 'U'],
'emp|thr': ['R'],
'emp|miw': ['R'],
'emp|nut': ['R'],
'emp|pha': ['R'],
'emp|lab': ['R'],
'emp|rad': ['R'],
},
'/ambulatory/encounter/[id]/process?menu=prescription': {
'emp|doc': ['R', 'U'],
'emp|nur': ['R'],
'emp|thr': ['R'],
'emp|miw': ['R'],
'emp|nut': ['R'],
'emp|pha': ['R'],
'emp|lab': ['R'],
'emp|rad': ['R'],
},
'/ambulatory/encounter/[id]/process?menu=device-order': {
'emp|doc': ['R', 'U'],
'emp|nur': ['R'],
'emp|thr': ['R'],
'emp|miw': ['R'],
'emp|nut': ['R'],
'emp|pha': ['R'],
'emp|lab': ['R'],
'emp|rad': ['R'],
},
'/ambulatory/encounter/[id]/process?menu=radiology-order': {
'emp|doc': ['R', 'U'],
'emp|nur': ['R'],
'emp|thr': ['R'],
'emp|miw': ['R'],
'emp|nut': ['R'],
'emp|pha': ['R'],
'emp|lab': ['R'],
'emp|rad': ['R'],
},
'/ambulatory/encounter/[id]/process?menu=cp-lab-order': {
'emp|doc': ['R', 'U'],
'emp|nur': ['R'],
'emp|thr': ['R'],
'emp|miw': ['R'],
'emp|nut': ['R'],
'emp|pha': ['R'],
'emp|lab': ['R'],
'emp|rad': ['R'],
},
'/ambulatory/encounter/[id]/process?menu=micro-lab-order': {
'emp|doc': ['R', 'U'],
'emp|nur': ['R'],
'emp|thr': ['R'],
'emp|miw': ['R'],
'emp|nut': ['R'],
'emp|pha': ['R'],
'emp|lab': ['R'],
'emp|rad': ['R'],
},
'/ambulatory/encounter/[id]/process?menu=pa-lab-order': {
'emp|doc': ['R', 'U'],
'emp|nur': ['R'],
'emp|thr': ['R'],
'emp|miw': ['R'],
'emp|nut': ['R'],
'emp|pha': ['R'],
'emp|lab': ['R'],
'emp|rad': ['R'],
},
'/ambulatory/consulation': {
'emp|doc': ['R'],
+41 -13
View File
@@ -13,6 +13,9 @@ import { genDoctor, type Doctor } from '~/models/doctor'
// Stores
import { useUserStore } from '~/stores/user'
// Handlers
import { uploadAttachmentCustom } from '~/handlers/supporting-document.handler'
// Services
import {
getList as getSpecialistList,
@@ -26,7 +29,6 @@ import {
} from '~/services/encounter.service'
import { getList as getMemberList } from '~/services/vclaim-member.service'
import { getList as getSepList } from '~/services/vclaim-sep.service'
import { uploadAttachment } from '~/services/supporting-document.service'
// Handlers
import {
@@ -468,6 +470,19 @@ export function useEncounterEntry(props: {
if (formData.subSpecialistId) {
await handleFetchDoctors(formData.subSpecialistId)
}
if (encounter.encounterDocuments && Array.isArray(encounter.encounterDocuments)) {
let sepFileReview = {}
let sippFileReview = {}
for (const doc of encounter.encounterDocuments) {
if (doc.type_code === 'vclaim-sep') {
sepFileReview = { id: doc.id, fileName: doc.fileName, filePath: doc.filePath, type: doc.type_code }
} else if (doc.type_code === 'vclaim-sipp') {
sippFileReview = { id: doc.id, fileName: doc.fileName, filePath: doc.filePath, type: doc.type_code }
}
}
formData.sepFileReview = sepFileReview
formData.sippFileReview = sippFileReview
}
formObjects.value = { ...formData }
}
@@ -501,6 +516,8 @@ export function useEncounterEntry(props: {
const visitDateValue = formValues.visitDate || formValues.registeredAt || formValues.registerDate || ''
const memberNumber = formValues.member_number ?? formValues.cardNumber ?? formValues.memberNumber ?? null
const refNumber = formValues.ref_number ?? formValues.sepNumber ?? formValues.refNumber ?? null
sepFile.value = formValues.sepFile || null
sippFile.value = formValues.sippFile || null
let paymentMethodCode = formValues.paymentMethod_code ?? null
if (!paymentMethodCode) {
@@ -517,7 +534,7 @@ export function useEncounterEntry(props: {
const payload: any = {
patient_id: patientId,
appointment_doctor_code: formValues.doctorId || null,
appointment_doctor_code: formValues.doctor_code || null,
class_code: props.classCode || '',
subClass_code: props.subClassCode || '',
infra_id: formValues.infra_id ?? null,
@@ -545,18 +562,21 @@ export function useEncounterEntry(props: {
if (paymentMethodCode) {
payload.paymentMethod_code = paymentMethodCode
}
if (!payload.vclaimReference) {
payload.vclaimReference = {
noSep: refNumber,
}
}
if (paymentMethodCode === 'insurance') {
payload.insuranceCompany_id = formValues.insuranceCompany_id ?? null
if (memberNumber) payload.member_number = memberNumber
// if (refNumber) payload.ref_number = refNumber
if (formValues.refTypeCode) payload.refTypeCode = formValues.refTypeCode
if (formValues.vclaimReference) payload.vclaimReference = formValues.vclaimReference
} else {
if (paymentMethodCode === 'membership' && memberNumber) {
payload.member_number = memberNumber
}
// if (refNumber) payload.ref_number = refNumber
}
if (isAdmin && props.classCode === 'ambulatory') {
@@ -573,16 +593,26 @@ export function useEncounterEntry(props: {
}
if (result.success) {
console.log(result)
console.log(sepFile.value)
console.log(sippFile.value)
// const encounterId = isEditMode.value ? props.id : result.body?.data?.id
if (patientId) {
const encounterId = isEditMode.value ? props.id : result.body?.data?.id
if (encounterId) {
if (sepFile.value) {
await uploadAttachment(sepFile.value, patientId, 'vclaim-sep')
await uploadAttachmentCustom({
id: isEditMode.value && formValues.sepFileReview ? formValues.sepFileReview.id : null,
file: sepFile.value,
refId: encounterId,
entityTypeCode: 'encounter',
type: 'vclaim-sep',
})
}
if (sippFile.value) {
await uploadAttachment(sippFile.value, patientId, 'vclaim-sipp')
await uploadAttachmentCustom({
id: isEditMode.value && formValues.sippFileReview ? formValues.sippFileReview.id : null,
file: sippFile.value,
refId: encounterId,
entityTypeCode: 'encounter',
type: 'vclaim-sipp',
})
}
}
@@ -620,8 +650,6 @@ export function useEncounterEntry(props: {
paymentsList,
sepsList,
sepNumber,
sepFile,
sippFile,
participantGroupsList,
specialistsTree,
doctorsList,
+37 -31
View File
@@ -62,17 +62,19 @@ const defaultKeys: Record<string, any> = {
classCode: ['ambulatory', 'emergency', 'inpatient'],
unit: 'all',
},
earlyNurseryAssessment: {
id: 'early-nursery-assessment',
title: 'Pengkajian Awal Keperawatan',
classCode: ['ambulatory', 'emergency', 'inpatient'],
unit: 'all',
},
// NOTE : HIDDEN UNTIL IT IS READY
// earlyNurseryAssessment: {
// id: 'early-nursery-assessment',
// title: 'Pengkajian Awal Keperawatan',
// classCode: ['ambulatory', 'emergency', 'inpatient'],
// unit: 'all',
// },
earlyMedicalAssessment: {
id: 'early-medical-assessment',
title: 'Pengkajian Awal Medis',
classCode: ['ambulatory', 'emergency', 'inpatient'],
unit: 'all',
afterId: 'early-medical-assessment',
},
earlyMedicalRehabAssessment: {
id: 'rehab-medical-assessment',
@@ -81,20 +83,27 @@ const defaultKeys: Record<string, any> = {
unit: 'rehab',
afterId: 'early-medical-assessment',
},
functionAssessment: {
id: 'function-assessment',
title: 'Asesmen Fungsi',
classCode: ['ambulatory'],
unit: 'rehab',
afterId: 'rehab-medical-assessment',
},
therapyProtocol: {
id: 'therapy-protocol',
classCode: ['ambulatory'],
title: 'Protokol Terapi',
unit: 'rehab',
afterId: 'function-assessment',
fkr: {
id: 'fkr',
title: 'FKR',
classCode: ['ambulatory', 'emergency', 'inpatient'],
unit: 'all',
},
// NOTE: Replaced by FRK
// functionAssessment: {
// id: 'function-assessment',
// title: 'Asesmen Fungsi',
// classCode: ['ambulatory'],
// unit: 'rehab',
// afterId: 'rehab-medical-assessment',
// },
// therapyProtocol: {
// id: 'therapy-protocol',
// classCode: ['ambulatory'],
// title: 'Protokol Terapi',
// unit: 'rehab',
// afterId: 'function-assessment',
// },
chemotherapyProtocol: {
id: 'chemotherapy-protocol',
title: 'Protokol Kemoterapi',
@@ -216,13 +225,7 @@ const defaultKeys: Record<string, any> = {
title: 'SPRI',
classCode: ['ambulatory', 'emergency'],
unit: 'all',
},
kfr: {
id: 'kfr',
title: 'KFR',
classCode: ['ambulatory', 'emergency', 'inpatient'],
unit: 'all',
},
},
refBack: {
id: 'reference-back',
title: 'PRB',
@@ -412,9 +415,9 @@ export function injectComponents(id: string | number, data: EncounterListData, m
currentKeys.refBack['component'] = PrbListAsync
currentKeys.refBack['props'] = { encounter: data?.encounter }
}
if (currentKeys?.kfr) {
currentKeys.kfr['component'] = KfrListAsync
currentKeys.kfr['props'] = { encounter: data?.encounter }
if (currentKeys?.fkr) {
currentKeys.fkr['component'] = KfrListAsync
currentKeys.fkr['props'] = { encounter: data?.encounter }
}
if (currentKeys?.screening) {
// TODO: add component for screening
@@ -499,9 +502,12 @@ export function mapResponseToEncounter(result: any): any {
? result.visitDate
: result.registeredAt || result.patient?.registeredAt || null,
adm_employee_id: result.adm_employee_id || 0,
appointment_doctor_id: result.appointment_doctor_id || null,
responsible_doctor_id: result.responsible_doctor_id || null,
adm_employee: result.adm_employee || null,
responsible_nurse_code: result.responsible_nurse_code || null,
responsible_nurse: result.responsible_nurse || null,
appointment_doctor_code: result.appointment_doctor_code || null,
appointment_doctor: result.appointment_doctor || null,
responsible_doctor_code: result.responsible_doctor_id || null,
responsible_doctor: result.responsible_doctor || null,
refSource_name: result.refSource_name || null,
appointment_id: result.appointment_id || null,
+6 -2
View File
@@ -9,7 +9,11 @@ export async function getEncounterData(id: string | number) {
try {
const dataRes = await getDetail(id, {
includes:
'patient,patient-person,patient-person-addresses,unit,Appointment_Doctor,Appointment_Doctor-employee,Appointment_Doctor-employee-person,Responsible_Doctor,Responsible_Doctor-employee,Responsible_Doctor-employee-person',
'patient,patient-person,patient-person-addresses,unit,' +
'Adm_Employee,Adm_Employee,Adm_Employee-Person,' +
'Responsible_Nurse,Responsible_Nurse-Employee,Responsible_Nurse-Employee-Person,' +
'Appointment_Doctor,Appointment_Doctor-employee,Appointment_Doctor-employee-person,' +
'Responsible_Doctor,Responsible_Doctor-employee,Responsible_Doctor-employee-person',
})
const dataResBody = dataRes.body ?? null
const result = dataResBody?.data ?? null
@@ -29,4 +33,4 @@ export async function getEncounterData(id: string | number) {
data = null
}
return data
}
}
@@ -256,6 +256,8 @@ export function useIntegrationSepEntry() {
},
]
} else {
const destA = lettersRaw?.rujukan?.provPerujuk?.Kode
const destB = lettersRaw?.rujukan?.provPerujuk?.kode
letters.value = [
{
letterNumber: lettersRaw?.rujukan?.noKunjungan || '',
@@ -276,7 +278,7 @@ export function useIntegrationSepEntry() {
patientName: lettersRaw?.rujukan?.peserta?.nama || '',
patientPhone: lettersRaw?.rujukan?.peserta?.mr?.noTelepon || '',
medicalRecordNumber: lettersRaw?.rujukan?.peserta?.mr?.noMR || '',
destination: lettersRaw?.rujukan?.provPerujuk?.kode || '',
destination: destA || destB || '',
},
},
]
@@ -480,7 +482,7 @@ export function useIntegrationSepEntry() {
}
if (!value.destinationClinic) {
mappedValues.destinationClinic = selectedObjects.value['destination'] || ''
mappedValues.referralTo = selectedObjects.value['destination'] || ''
}
if (!value.clinicExcecutive) {
mappedValues.clinicExcecutive = 'no'
@@ -22,3 +22,17 @@ export const {
update,
remove,
})
export async function uploadAttachmentCustom(payload: any) {
const { user } = useUserStore()
const formData = new FormData()
formData.append('content', payload.file)
formData.append('entityType_code', payload.entityTypeCode)
formData.append('type_code', payload.type)
formData.append('ref_id', payload.refId)
formData.append('upload_employee_id', user.employee_id)
const response = payload.id ? await update(payload.id, formData) : await create(formData)
return response?.body?.data
}
+1 -1
View File
@@ -52,7 +52,7 @@ export function genEncounter(): Encounter {
patient: genPatient(),
registeredAt: '',
class_code: '',
unit_code: 0,
unit_code: '',
unit: genUnit(),
visitDate: '',
adm_employee_id: 0,
+2
View File
@@ -1,7 +1,9 @@
import { type Base, genBase } from "./_base"
import type { Employee } from "./employee"
export interface Nurse extends Base {
employee_id: number
employee?: Employee
ihs_number?: string
unit_id: number
infra_id: number
@@ -12,7 +12,7 @@ import Error from '~/components/pub/my-ui/error/error.vue'
// Apps
import Content from '~/components/content/encounter/process.vue'
const { getRouteTitle, getPageAccess } = usePageChecker()
const { getRouteTitle, getPageAccess, hasCreateAccess, hasReadAccess, hasUpdateAccess, hasDeleteAccess } = usePageChecker()
definePageMeta({
middleware: ['rbac'],
@@ -28,16 +28,32 @@ useHead({
})
// Preps role checking
const roleAccess: Record<string, Permission[]> = permissions['/ambulatory/encounter/[id]/process'] || {}
const hasAccess = getPageAccess(roleAccess, 'read')
const route = useRoute()
const menu = computed(() => route.query.menu as string | undefined)
const accessKey = computed(() => `/ambulatory/encounter/[id]/process` + (menu.value ? `?menu=${menu.value}` : ''))
const roleAccess: Record<string, Permission[]> = permissions[accessKey.value] || {}
const hasAccess = getPageAccess(roleAccess, 'read') || true
const canCreate = hasCreateAccess(roleAccess)
const canRead = hasReadAccess(roleAccess)
const canUpdate = hasUpdateAccess(roleAccess)
const canDelete = hasDeleteAccess(roleAccess)
</script>
<template>
<!--
{{ accessKey }}
{{ roleAccess }}
{{ hasAccess }}
{{ canUpdate }}
-->
<div v-if="hasAccess">
<Content class-code="ambulatory" />
<Content
class-code="ambulatory"
:can-create="canCreate"
:can-read="canRead"
:can-update="canUpdate"
:can-delete="canDelete"
/>
</div>
<Error
v-else
:status-code="403"
/>
<Error v-else :status-code="403" />
</template>
+3 -2
View File
@@ -4,8 +4,9 @@ import { InternalReferenceSchema } from './internal-reference.schema'
// Check In
const CheckInSchema = z.object({
// registeredAt: z.string({ required_error: 'Tanggal masuk harus diisi' }),
responsible_doctor_id: z.number({ required_error: 'Dokter harus diisi' }).gt(0, 'Dokter harus diisi'),
adm_employee_id: z.number({ required_error: 'PJA harus diisi' }).gt(0, 'PJA harus diisi'),
responsible_doctor_code: z.string({ required_error: 'Dokter harus diisi' }),
// adm_employee_id: z.number({ required_error: 'PJA harus diisi' }).gt(0, 'PJA harus diisi'),
registeredAt: z.string({ required_error: 'waktu harus diisi' }),
})
type CheckInFormData = z.infer<typeof CheckInSchema>