Merge pull request #194 from dikstub-rssa/feat/page-cleaning

Feat/page cleaning
This commit is contained in:
Andsky
2025-12-02 10:02:35 +07:00
committed by GitHub
11 changed files with 926 additions and 299 deletions
+185 -149
View File
@@ -1,43 +1,57 @@
<script setup lang="ts"> <script setup lang="ts">
// Components // Components
import Block from '~/components/pub/my-ui/doc-entry/block.vue' import * as DE from '~/components/pub/my-ui/doc-entry'
import Cell from '~/components/pub/my-ui/doc-entry/cell.vue'
import Field from '~/components/pub/my-ui/doc-entry/field.vue'
import Label from '~/components/pub/my-ui/doc-entry/label.vue'
import { Button } from '~/components/pub/ui/button' import { Button } from '~/components/pub/ui/button'
import { Input } from '~/components/pub/ui/input' import { Input } from '~/components/pub/ui/input'
import Select from '~/components/pub/ui/select/Select.vue' import * as CB from '~/components/pub/my-ui/combobox'
import Combobox from '~/components/pub/my-ui/combobox/combobox.vue'
import DatepickerSingle from '~/components/pub/my-ui/datepicker/datepicker-single.vue' import DatepickerSingle from '~/components/pub/my-ui/datepicker/datepicker-single.vue'
import TreeSelect from '~/components/pub/my-ui/select-tree/tree-select.vue'
import FileUpload from '~/components/pub/my-ui/form/file-field.vue' import FileUpload from '~/components/pub/my-ui/form/file-field.vue'
// Types // Types
import { IntegrationEncounterSchema, type IntegrationEncounterFormData } from '~/schemas/integration-encounter.schema' import { IntegrationEncounterSchema, type IntegrationEncounterFormData } from '~/schemas/integration-encounter.schema'
import type { PatientEntity } from '~/models/patient' import type { PatientEntity } from '~/models/patient'
import type { TreeItem } from '~/components/pub/my-ui/select-tree/type'
// Helpers // Helpers
import { toTypedSchema } from '@vee-validate/zod' import { toTypedSchema } from '@vee-validate/zod'
import { useForm } from 'vee-validate' import { useForm } from 'vee-validate'
import { refDebounced } from '@vueuse/core' import { refDebounced } from '@vueuse/core'
import type { Doctor } from '~/models/doctor'
// References
import { paymentMethodCodes } from '~/const/key-val/common'
// App things
import { genEncounter, type Encounter } from '~/models/encounter'
// Props
const props = defineProps<{ const props = defineProps<{
isLoading?: boolean isLoading?: boolean
isReadonly?: boolean isReadonly?: boolean
isSepValid?: boolean isSepValid?: boolean
isCheckingSep?: boolean isCheckingSep?: boolean
doctor?: any[] doctorItems?: CB.Item[]
subSpecialist?: any[] selectedDoctor: Doctor
specialists?: TreeItem[] // subSpecialist?: any[]
payments: any[] // specialists?: TreeItem[]
// paymentMethods: PaymentMethodCode[]
participantGroups?: any[] participantGroups?: any[]
seps: any[] seps: any[]
patient?: PatientEntity | null | undefined patient?: PatientEntity | null | undefined
objects?: any // objects?: any
}>() }>()
// Model
const model = defineModel<Encounter>()
model.value = genEncounter()
// Common preparation
const defaultCBItems = [{ label: 'Pilih', value: '' }];
const paymentMethodItems = CB.recStrToItem(paymentMethodCodes)
// Emit preparation
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'onSelectDoctor', code: string): void
(e: 'event', menu: string, value?: any): void (e: 'event', menu: string, value?: any): void
(e: 'fetch', value?: any): void (e: 'fetch', value?: any): void
}>() }>()
@@ -48,10 +62,10 @@ const { handleSubmit, errors, defineField, meta } = useForm<IntegrationEncounter
}) })
// Bind fields and extract attrs // Bind fields and extract attrs
const [doctorId, doctorIdAttrs] = defineField('doctorId') const [doctorCode, doctorCodeAttrs] = defineField('doctor_code')
const [subSpecialistId, subSpecialistIdAttrs] = defineField('subSpecialistId') const [unitCode, unitCodeAttrs] = defineField('unit_code')
const [registerDate, registerDateAttrs] = defineField('registerDate') const [registerDate, registerDateAttrs] = defineField('registerDate')
const [paymentType, paymentTypeAttrs] = defineField('paymentType') const [paymentMethodCode, paymentMethodCodeAttrs] = defineField('paymentMethod_code')
const [patientCategory, patientCategoryAttrs] = defineField('patientCategory') const [patientCategory, patientCategoryAttrs] = defineField('patientCategory')
const [cardNumber, cardNumberAttrs] = defineField('cardNumber') const [cardNumber, cardNumberAttrs] = defineField('cardNumber')
const [sepType, sepTypeAttrs] = defineField('sepType') const [sepType, sepTypeAttrs] = defineField('sepType')
@@ -68,30 +82,50 @@ const isReadonly = props.isReadonly !== undefined ? props.isReadonly : false
const isSepValid = computed(() => props.isSepValid || false) const isSepValid = computed(() => props.isSepValid || false)
const isCheckingSep = computed(() => props.isCheckingSep || false) const isCheckingSep = computed(() => props.isCheckingSep || false)
const doctorOpts = computed(() => { // Unit, specialist, subspecialist
// Add default option const unitFullName = ref('')
const defaultOption = [{ label: 'Pilih', value: '' }] watch(() => props.selectedDoctor, (doctor) => {
// Add doctors from props unitFullName.value = doctor.subspecialist?.name ??
const doctors = props.doctor || [] doctor.specialist?.name ??
return [...defaultOption, ...doctors] 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 || ''
},
)
// const doctorOpts = computed(() => {
// const defaultOption = [{ label: 'Pilih', value: '' }]
// const doctors = props.doctors || []
// return [...defaultOption, ...doctors]
// })
// watch(doctorCode, (newValue) => {
// // doctor.value = props.doctors?.find(doc => doc.code === newValue)
// unitFullName.value = doctor.value?.subspecialist?.name ??
// doctor.value?.specialist?.name ??
// doctor.value?.unit?.name ??
// 'tidak diketahui'
// model.value!.responsible_doctor_code = doctor.value?.code
// // const unitName = selectedDoctor?.specialist?.name || ''
// // emit('event', 'unit-changed', unitName)
// })
const isJKNPayment = computed(() => paymentType.value === 'jkn') const isJKNPayment = computed(() => paymentMethodCode.value === 'jkn')
async function onFetchChildren(parentId: string): Promise<void> { // async function onFetchChildren(parentId: string): Promise<void> {
console.log('onFetchChildren', parentId) // console.log('onFetchChildren', parentId)
} // }
// Watch specialist/subspecialist selection to fetch doctors // Watch specialist/subspecialist selection to fetch doctors
watch(subSpecialistId, async (newValue) => { // watch(subSpecialistCode, async (newValue) => {
if (newValue) { // if (newValue) {
console.log('SubSpecialist changed:', newValue) // console.log('SubSpecialist changed:', newValue)
// Reset doctor selection // // Reset doctor selection
doctorId.value = '' // doctorCode.value = ''
// Emit fetch event to parent // // Emit fetch event to parent
emit('fetch', { subSpecialistId: newValue }) // emit('fetch', { subSpecialistCode: newValue })
} // }
}) // })
// Debounced SEP number watcher: emit change only after user stops typing // Debounced SEP number watcher: emit change only after user stops typing
const debouncedSepNumber = refDebounced(sepNumber, 500) const debouncedSepNumber = refDebounced(sepNumber, 500)
@@ -100,25 +134,25 @@ watch(debouncedSepNumber, (newValue) => {
}) })
// Sync props to form fields // Sync props to form fields
watch( // watch(
() => props.objects, // () => props.objects,
(objects) => { // (objects) => {
if (objects && Object.keys(objects).length > 0) { // if (objects && Object.keys(objects).length > 0) {
patientName.value = objects?.patientName || '' // patientName.value = objects?.patientName || ''
nationalIdentity.value = objects?.nationalIdentity || '' // nationalIdentity.value = objects?.nationalIdentity || ''
medicalRecordNumber.value = objects?.medicalRecordNumber || '' // medicalRecordNumber.value = objects?.medicalRecordNumber || ''
doctorId.value = objects?.doctorId || '' // doctorCode.value = objects?.doctorCode || ''
subSpecialistId.value = objects?.subSpecialistId || '' // subSpecialistCode.value = objects?.subSpecialistCode || ''
registerDate.value = objects?.registerDate || '' // registerDate.value = objects?.registerDate || ''
paymentType.value = objects?.paymentType || '' // paymentMethodCode.value = objects?.paymentMethodCode || ''
patientCategory.value = objects?.patientCategory || '' // patientCategory.value = objects?.patientCategory || ''
cardNumber.value = objects?.cardNumber || '' // cardNumber.value = objects?.cardNumber || ''
sepType.value = objects?.sepType || '' // sepType.value = objects?.sepType || ''
sepNumber.value = objects?.sepNumber || '' // sepNumber.value = objects?.sepNumber || ''
} // }
}, // },
{ deep: true, immediate: true }, // { deep: true, immediate: true },
) // )
watch( watch(
() => props.patient, () => props.patient,
@@ -136,11 +170,11 @@ watch(
function onAddSep() { function onAddSep() {
const formValues = { const formValues = {
patientId: patientId.value || '', patientId: patientId.value || '',
doctorCode: doctorId.value, doctorCode: doctorCode.value,
subSpecialistCode: subSpecialistId.value, // subSpecialistCode: subSpecialistCode.value,
registerDate: registerDate.value, registerDate: registerDate.value,
cardNumber: cardNumber.value, cardNumber: cardNumber.value,
paymentType: paymentType.value, paymentMethodCode: paymentMethodCode.value,
sepType: sepType.value sepType: sepType.value
} }
emit('event', 'add-sep', formValues) emit('event', 'add-sep', formValues)
@@ -158,10 +192,10 @@ const formRef = ref<HTMLFormElement | null>(null)
function submitForm() { function submitForm() {
console.log('🔵 submitForm called, formRef:', formRef.value) console.log('🔵 submitForm called, formRef:', formRef.value)
console.log('🔵 Form values:', { console.log('🔵 Form values:', {
doctorId: doctorId.value, doctorCode: doctorCode.value,
subSpecialistId: subSpecialistId.value, // subSpecialistCode: subSpecialistCode.value,
registerDate: registerDate.value, registerDate: registerDate.value,
paymentType: paymentType.value, paymentMethodCode: paymentMethodCode.value,
}) })
console.log('🔵 Form errors:', errors.value) console.log('🔵 Form errors:', errors.value)
console.log('🔵 Form meta:', meta.value) console.log('🔵 Form meta:', meta.value)
@@ -231,149 +265,151 @@ defineExpose({
</div> </div>
</div> </div>
<Block <DE.Block
labelSize="thin" labelSize="thin"
class="!pt-0" class="!pt-0"
:colCount="3" :colCount="3"
:cellFlex="false" :cellFlex="false"
> >
<Cell> <DE.Cell>
<Label height="compact">Nama Pasien</Label> <DE.Label height="compact">Nama Pasien</DE.Label>
<Field :errMessage="errors.patientName"> <DE.Field :errMessage="errors.patientName">
<Input <Input
id="patientName" id="patientName"
v-model="patientName" v-model="patientName"
v-bind="patientNameAttrs" v-bind="patientNameAttrs"
:disabled="true" :disabled="true"
/> />
</Field> </DE.Field>
</Cell> </DE.Cell>
<Cell> <DE.Cell>
<Label height="compact">NIK</Label> <DE.Label height="compact">NIK</DE.Label>
<Field :errMessage="errors.nationalIdentity"> <DE.Field :errMessage="errors.nationalIdentity">
<Input <Input
id="nationalIdentity" id="nationalIdentity"
v-model="nationalIdentity" v-model="nationalIdentity"
v-bind="nationalIdentityAttrs" v-bind="nationalIdentityAttrs"
:disabled="true" :disabled="true"
/> />
</Field> </DE.Field>
</Cell> </DE.Cell>
<Cell> <DE.Cell>
<Label height="compact">No. RM</Label> <DE.Label height="compact">No. RM</DE.Label>
<Field :errMessage="errors.medicalRecordNumber"> <DE.Field :errMessage="errors.medicalRecordNumber">
<Input <Input
id="medicalRecordNumber" id="medicalRecordNumber"
v-model="medicalRecordNumber" v-model="medicalRecordNumber"
v-bind="medicalRecordNumberAttrs" v-bind="medicalRecordNumberAttrs"
:disabled="true" :disabled="true"
/> />
</Field> </DE.Field>
</Cell> </DE.Cell>
</Block> </DE.Block>
<hr /> <hr />
<!-- Data Kunjungan --> <!-- Data Kunjungan -->
<h3 class="text-lg font-semibold">Data Kunjungan</h3> <h3 class="text-lg font-semibold">Data Kunjungan</h3>
<Block <DE.Block
labelSize="thin" labelSize="thin"
class="!pt-0" class="!pt-0"
:colCount="3" :colCount="3"
:cellFlex="false" :cellFlex="false"
> >
<Cell> <DE.Cell>
<Label height="compact"> <DE.Label height="compact">
Dokter Dokter
<span class="text-red-500">*</span> <span class="text-red-500">*</span>
</Label> </DE.Label>
<Field :errMessage="errors.doctorId"> <DE.Field :errMessage="errors.doctor_code">
<Combobox <CB.Combobox
id="doctorId" id="doctorCode"
v-model="doctorId" v-model="doctorCode"
v-bind="doctorIdAttrs" v-bind="doctorCodeAttrs"
:items="doctorOpts" :items="[...defaultCBItems, ...doctorItems]"
:is-disabled="isLoading || isReadonly" :is-disabled="isLoading || isReadonly"
placeholder="Pilih Dokter" placeholder="Pilih Dokter"
search-placeholder="Cari Dokter" search-placeholder="Cari Dokter"
empty-message="Dokter tidak ditemukan" empty-message="Dokter tidak ditemukan"
@update:model-value="(value) => emit('onSelectDoctor', value)"
/> />
</Field> </DE.Field>
</Cell> </DE.Cell>
<Cell> <DE.Cell>
<Label height="compact"> <DE.Label height="compact">
Spesialis / Subspesialis Spesialis / Subspesialis
<span class="text-red-500">*</span> <span class="text-red-500">*</span>
</Label> </DE.Label>
<Field :errMessage="errors.subSpecialistId"> <DE.Field :errMessage="errors.unit_code">
<TreeSelect <Input :value="unitFullName"/>
id="subSpecialistId" <!-- <TreeSelect
v-model="subSpecialistId" id="subSpecialistCode"
v-bind="subSpecialistIdAttrs" v-model="subSpecialistCode"
v-bind="subSpecialistCodeAttrs"
:data="specialists || []" :data="specialists || []"
:on-fetch-children="onFetchChildren" :on-fetch-children="onFetchChildren"
/> /> -->
</Field> </DE.Field>
</Cell> </DE.Cell>
</Block> </DE.Block>
<Block <DE.Block
labelSize="thin" labelSize="thin"
class="!pt-0" class="!pt-0"
:colCount="3" :colCount="3"
:cellFlex="false" :cellFlex="false"
> >
<Cell> <DE.Cell>
<Label height="compact"> <DE.Label height="compact">
Tanggal Daftar Tanggal Daftar
<span class="text-red-500">*</span> <span class="text-red-500">*</span>
</Label> </DE.Label>
<Field :errMessage="errors.registerDate"> <DE.Field :errMessage="errors.registerDate">
<DatepickerSingle <DatepickerSingle
id="registerDate" id="registerDate"
v-model="registerDate" v-model="registerDate"
v-bind="registerDateAttrs" v-bind="registerDateAttrs"
placeholder="Pilih tanggal" placeholder="Pilih tanggal"
/> />
</Field> </DE.Field>
</Cell> </DE.Cell>
<Cell> <DE.Cell>
<Label height="compact"> <DE.Label height="compact">
Jenis Pembayaran Jenis Pembayaran
<span class="text-red-500">*</span> <span class="text-red-500">*</span>
</Label> </DE.Label>
<Field :errMessage="errors.paymentType"> <DE.Field :errMessage="errors.paymentMethod_code">
<Select <CB.Combobox
id="paymentType" id="paymentMethodCode"
v-model="paymentType" v-model="paymentMethodCode"
v-bind="paymentTypeAttrs" v-bind="paymentMethodCodeAttrs"
:items="payments" :items="paymentMethodItems"
:disabled="isLoading || isReadonly" :disabled="isLoading || isReadonly"
placeholder="Pilih Jenis Pembayaran" placeholder="Pilih Jenis Pembayaran"
/> />
</Field> </DE.Field>
</Cell> </DE.Cell>
</Block> </DE.Block>
<!-- BPJS Fields (conditional) --> <!-- BPJS Fields (conditional) -->
<template v-if="isJKNPayment"> <template v-if="isJKNPayment">
<Block <DE.Block
labelSize="thin" labelSize="thin"
class="!pt-0" class="!pt-0"
:colCount="3" :colCount="3"
:cellFlex="false" :cellFlex="false"
> >
<Cell> <DE.Cell>
<Label height="compact"> <DE.Label height="compact">
Kelompok Peserta Kelompok Peserta
<span class="text-red-500">*</span> <span class="text-red-500">*</span>
</Label> </DE.Label>
<Field :errMessage="errors.patientCategory"> <DE.Field :errMessage="errors.patientCategory">
<Select <Select
id="patientCategory" id="patientCategory"
v-model="patientCategory" v-model="patientCategory"
@@ -382,15 +418,15 @@ defineExpose({
:disabled="isLoading || isReadonly" :disabled="isLoading || isReadonly"
placeholder="Pilih Kelompok Peserta" placeholder="Pilih Kelompok Peserta"
/> />
</Field> </DE.Field>
</Cell> </DE.Cell>
<Cell> <DE.Cell>
<Label height="compact"> <DE.Label height="compact">
No. Kartu BPJS No. Kartu BPJS
<span class="text-red-500">*</span> <span class="text-red-500">*</span>
</Label> </DE.Label>
<Field :errMessage="errors.cardNumber"> <DE.Field :errMessage="errors.cardNumber">
<Input <Input
id="cardNumber" id="cardNumber"
v-model="cardNumber" v-model="cardNumber"
@@ -398,15 +434,15 @@ defineExpose({
:disabled="isLoading || isReadonly" :disabled="isLoading || isReadonly"
placeholder="Masukkan nomor kartu BPJS" placeholder="Masukkan nomor kartu BPJS"
/> />
</Field> </DE.Field>
</Cell> </DE.Cell>
<Cell> <DE.Cell>
<Label height="compact"> <DE.Label height="compact">
Jenis SEP Jenis SEP
<span class="text-red-500">*</span> <span class="text-red-500">*</span>
</Label> </DE.Label>
<Field :errMessage="errors.sepType"> <DE.Field :errMessage="errors.sepType">
<Select <Select
id="sepType" id="sepType"
v-model="sepType" v-model="sepType"
@@ -415,22 +451,22 @@ defineExpose({
:disabled="isLoading || isReadonly" :disabled="isLoading || isReadonly"
placeholder="Pilih Jenis SEP" placeholder="Pilih Jenis SEP"
/> />
</Field> </DE.Field>
</Cell> </DE.Cell>
</Block> </DE.Block>
<Block <DE.Block
labelSize="thin" labelSize="thin"
class="!pt-0" class="!pt-0"
:colCount="3" :colCount="3"
:cellFlex="false" :cellFlex="false"
> >
<Cell> <DE.Cell>
<Label height="compact"> <DE.Label height="compact">
No. SEP No. SEP
<span class="text-red-500">*</span> <span class="text-red-500">*</span>
</Label> </DE.Label>
<Field :errMessage="errors.sepNumber"> <DE.Field :errMessage="errors.sepNumber">
<div class="flex gap-2"> <div class="flex gap-2">
<Input <Input
id="sepNumber" id="sepNumber"
@@ -474,8 +510,8 @@ defineExpose({
/> />
</Button> </Button>
</div> </div>
</Field> </DE.Field>
</Cell> </DE.Cell>
<FileUpload <FileUpload
field-name="sepFile" field-name="sepFile"
@@ -492,7 +528,7 @@ defineExpose({
:accept="['pdf', 'jpg', 'png']" :accept="['pdf', 'jpg', 'png']"
:max-size-mb="1" :max-size-mb="1"
/> />
</Block> </DE.Block>
</template> </template>
</form> </form>
</div> </div>
@@ -0,0 +1,499 @@
<script setup lang="ts">
// Components
import Block from '~/components/pub/my-ui/doc-entry/block.vue'
import Cell from '~/components/pub/my-ui/doc-entry/cell.vue'
import Field from '~/components/pub/my-ui/doc-entry/field.vue'
import Label from '~/components/pub/my-ui/doc-entry/label.vue'
import { Button } from '~/components/pub/ui/button'
import { Input } from '~/components/pub/ui/input'
import Select from '~/components/pub/ui/select/Select.vue'
import Combobox from '~/components/pub/my-ui/combobox/combobox.vue'
import DatepickerSingle from '~/components/pub/my-ui/datepicker/datepicker-single.vue'
import TreeSelect from '~/components/pub/my-ui/select-tree/tree-select.vue'
import FileUpload from '~/components/pub/my-ui/form/file-field.vue'
// Types
import { IntegrationEncounterSchema, type IntegrationEncounterFormData } from '~/schemas/integration-encounter.schema'
import type { PatientEntity } from '~/models/patient'
import type { TreeItem } from '~/components/pub/my-ui/select-tree/type'
// Helpers
import { toTypedSchema } from '@vee-validate/zod'
import { useForm } from 'vee-validate'
import { refDebounced } from '@vueuse/core'
const props = defineProps<{
isLoading?: boolean
isReadonly?: boolean
isSepValid?: boolean
isCheckingSep?: boolean
doctor?: any[]
subSpecialist?: any[]
specialists?: TreeItem[]
payments: any[]
participantGroups?: any[]
seps: any[]
patient?: PatientEntity | null | undefined
objects?: any
}>()
const emit = defineEmits<{
(e: 'event', menu: string, value?: any): void
(e: 'fetch', value?: any): void
}>()
// Validation schema
const { handleSubmit, errors, defineField, meta } = useForm<IntegrationEncounterFormData>({
validationSchema: toTypedSchema(IntegrationEncounterSchema),
})
// Bind fields and extract attrs
const [doctorId, doctorIdAttrs] = defineField('doctorId')
const [subSpecialistId, subSpecialistIdAttrs] = defineField('subSpecialistId')
const [registerDate, registerDateAttrs] = defineField('registerDate')
const [paymentType, paymentTypeAttrs] = defineField('paymentType')
const [patientCategory, patientCategoryAttrs] = defineField('patientCategory')
const [cardNumber, cardNumberAttrs] = defineField('cardNumber')
const [sepType, sepTypeAttrs] = defineField('sepType')
const [sepNumber, sepNumberAttrs] = defineField('sepNumber')
const [patientName, patientNameAttrs] = defineField('patientName')
const [nationalIdentity, nationalIdentityAttrs] = defineField('nationalIdentity')
const [medicalRecordNumber, medicalRecordNumberAttrs] = defineField('medicalRecordNumber')
const patientId = ref('')
const isLoading = props.isLoading !== undefined ? props.isLoading : false
const isReadonly = props.isReadonly !== undefined ? props.isReadonly : false
// SEP validation state from props
const isSepValid = computed(() => props.isSepValid || false)
const isCheckingSep = computed(() => props.isCheckingSep || false)
const doctorOpts = computed(() => {
// Add default option
const defaultOption = [{ label: 'Pilih', value: '' }]
// Add doctors from props
const doctors = props.doctor || []
return [...defaultOption, ...doctors]
})
const isJKNPayment = computed(() => paymentType.value === 'jkn')
async function onFetchChildren(parentId: string): Promise<void> {
console.log('onFetchChildren', parentId)
}
// Watch specialist/subspecialist selection to fetch doctors
watch(subSpecialistId, async (newValue) => {
if (newValue) {
console.log('SubSpecialist changed:', newValue)
// Reset doctor selection
doctorId.value = ''
// Emit fetch event to parent
emit('fetch', { subSpecialistId: newValue })
}
})
// Debounced SEP number watcher: emit change only after user stops typing
const debouncedSepNumber = refDebounced(sepNumber, 500)
watch(debouncedSepNumber, (newValue) => {
emit('event', 'sep-number-changed', newValue)
})
// Sync props to form fields
watch(
() => props.objects,
(objects) => {
if (objects && Object.keys(objects).length > 0) {
patientName.value = objects?.patientName || ''
nationalIdentity.value = objects?.nationalIdentity || ''
medicalRecordNumber.value = objects?.medicalRecordNumber || ''
doctorId.value = objects?.doctorId || ''
subSpecialistId.value = objects?.subSpecialistId || ''
registerDate.value = objects?.registerDate || ''
paymentType.value = objects?.paymentType || ''
patientCategory.value = objects?.patientCategory || ''
cardNumber.value = objects?.cardNumber || ''
sepType.value = objects?.sepType || ''
sepNumber.value = objects?.sepNumber || ''
}
},
{ deep: true, immediate: true },
)
watch(
() => props.patient,
(patient) => {
if (patient && Object.keys(patient).length > 0) {
patientId.value = patient?.id ? String(patient.id) : ''
patientName.value = patient?.person?.name || ''
nationalIdentity.value = patient?.person?.residentIdentityNumber || ''
medicalRecordNumber.value = patient?.number || ''
}
},
{ deep: true, immediate: true },
)
function onAddSep() {
const formValues = {
patientId: patientId.value || '',
doctorCode: doctorId.value,
subSpecialistCode: subSpecialistId.value,
registerDate: registerDate.value,
cardNumber: cardNumber.value,
paymentType: paymentType.value,
sepType: sepType.value
}
emit('event', 'add-sep', formValues)
}
// Submit handler
const onSubmit = handleSubmit((values) => {
console.log('✅ Validated form values:', JSON.stringify(values, null, 2))
emit('event', 'save', values)
})
// Expose submit method for parent component
const formRef = ref<HTMLFormElement | null>(null)
function submitForm() {
console.log('🔵 submitForm called, formRef:', formRef.value)
console.log('🔵 Form values:', {
doctorId: doctorId.value,
subSpecialistId: subSpecialistId.value,
registerDate: registerDate.value,
paymentType: paymentType.value,
})
console.log('🔵 Form errors:', errors.value)
console.log('🔵 Form meta:', meta.value)
// Trigger form submit using native form submit
// This will trigger validation and onSubmit handler
if (formRef.value) {
console.log('🔵 Calling formRef.value.requestSubmit()')
formRef.value.requestSubmit()
} else {
console.warn('⚠️ formRef.value is null, cannot submit form')
// Fallback: directly call onSubmit handler
// Create a mock event object
const mockEvent = {
preventDefault: () => {},
target: formRef.value || {},
} as SubmitEvent
// Call onSubmit directly
console.log('🔵 Calling onSubmit with mock event')
onSubmit(mockEvent)
}
}
defineExpose({
submitForm,
})
</script>
<template>
<div class="mx-auto w-full">
<form
ref="formRef"
@submit.prevent="onSubmit"
class="grid gap-6 p-4"
>
<!-- Data Pasien -->
<div class="flex flex-col gap-2">
<h3 class="text-lg font-semibold">Data Pasien</h3>
<div class="flex items-center gap-2">
<span class="text-sm">sudah pernah terdaftar sebagai pasien?</span>
<Button
variant="outline"
type="button"
class="h-[40px] rounded-md border-orange-400 text-orange-400 hover:bg-green-50"
@click="emit('event', 'search')"
>
<Icon
name="i-lucide-search"
class="h-5 w-5"
/>
Cari Pasien
</Button>
<span class="text-sm">belum pernah terdaftar sebagai pasien?</span>
<Button
variant="outline"
type="button"
class="h-[40px] rounded-md border-orange-400 text-orange-400 hover:bg-green-50"
@click="emit('event', 'add')"
>
<Icon
name="i-lucide-plus"
class="h-5 w-5"
/>
Tambah Pasien Baru
</Button>
</div>
</div>
<Block
labelSize="thin"
class="!pt-0"
:colCount="3"
:cellFlex="false"
>
<Cell>
<Label height="compact">Nama Pasien</Label>
<Field :errMessage="errors.patientName">
<Input
id="patientName"
v-model="patientName"
v-bind="patientNameAttrs"
:disabled="true"
/>
</Field>
</Cell>
<Cell>
<Label height="compact">NIK</Label>
<Field :errMessage="errors.nationalIdentity">
<Input
id="nationalIdentity"
v-model="nationalIdentity"
v-bind="nationalIdentityAttrs"
:disabled="true"
/>
</Field>
</Cell>
<Cell>
<Label height="compact">No. RM</Label>
<Field :errMessage="errors.medicalRecordNumber">
<Input
id="medicalRecordNumber"
v-model="medicalRecordNumber"
v-bind="medicalRecordNumberAttrs"
:disabled="true"
/>
</Field>
</Cell>
</Block>
<hr />
<!-- Data Kunjungan -->
<h3 class="text-lg font-semibold">Data Kunjungan</h3>
<Block
labelSize="thin"
class="!pt-0"
:colCount="3"
:cellFlex="false"
>
<Cell>
<Label height="compact">
Dokter
<span class="text-red-500">*</span>
</Label>
<Field :errMessage="errors.doctorId">
<Combobox
id="doctorId"
v-model="doctorId"
v-bind="doctorIdAttrs"
:items="doctorOpts"
:is-disabled="isLoading || isReadonly"
placeholder="Pilih Dokter"
search-placeholder="Cari Dokter"
empty-message="Dokter tidak ditemukan"
/>
</Field>
</Cell>
<Cell>
<Label height="compact">
Spesialis / Subspesialis
<span class="text-red-500">*</span>
</Label>
<Field :errMessage="errors.subSpecialistId">
<TreeSelect
id="subSpecialistId"
v-model="subSpecialistId"
v-bind="subSpecialistIdAttrs"
:data="specialists || []"
:on-fetch-children="onFetchChildren"
/>
</Field>
</Cell>
</Block>
<Block
labelSize="thin"
class="!pt-0"
:colCount="3"
:cellFlex="false"
>
<Cell>
<Label height="compact">
Tanggal Daftar
<span class="text-red-500">*</span>
</Label>
<Field :errMessage="errors.registerDate">
<DatepickerSingle
id="registerDate"
v-model="registerDate"
v-bind="registerDateAttrs"
placeholder="Pilih tanggal"
/>
</Field>
</Cell>
<Cell>
<Label height="compact">
Jenis Pembayaran
<span class="text-red-500">*</span>
</Label>
<Field :errMessage="errors.paymentType">
<Select
id="paymentType"
v-model="paymentType"
v-bind="paymentTypeAttrs"
:items="payments"
:disabled="isLoading || isReadonly"
placeholder="Pilih Jenis Pembayaran"
/>
</Field>
</Cell>
</Block>
<!-- BPJS Fields (conditional) -->
<template v-if="isJKNPayment">
<Block
labelSize="thin"
class="!pt-0"
:colCount="3"
:cellFlex="false"
>
<Cell>
<Label height="compact">
Kelompok Peserta
<span class="text-red-500">*</span>
</Label>
<Field :errMessage="errors.patientCategory">
<Select
id="patientCategory"
v-model="patientCategory"
v-bind="patientCategoryAttrs"
:items="participantGroups || []"
:disabled="isLoading || isReadonly"
placeholder="Pilih Kelompok Peserta"
/>
</Field>
</Cell>
<Cell>
<Label height="compact">
No. Kartu BPJS
<span class="text-red-500">*</span>
</Label>
<Field :errMessage="errors.cardNumber">
<Input
id="cardNumber"
v-model="cardNumber"
v-bind="cardNumberAttrs"
:disabled="isLoading || isReadonly"
placeholder="Masukkan nomor kartu BPJS"
/>
</Field>
</Cell>
<Cell>
<Label height="compact">
Jenis SEP
<span class="text-red-500">*</span>
</Label>
<Field :errMessage="errors.sepType">
<Select
id="sepType"
v-model="sepType"
v-bind="sepTypeAttrs"
:items="seps"
:disabled="isLoading || isReadonly"
placeholder="Pilih Jenis SEP"
/>
</Field>
</Cell>
</Block>
<Block
labelSize="thin"
class="!pt-0"
:colCount="3"
:cellFlex="false"
>
<Cell>
<Label height="compact">
No. SEP
<span class="text-red-500">*</span>
</Label>
<Field :errMessage="errors.sepNumber">
<div class="flex gap-2">
<Input
id="sepNumber"
v-model="sepNumber"
v-bind="sepNumberAttrs"
placeholder="Tambah SEP terlebih dahulu"
class="flex-1"
:disabled="isLoading || isReadonly"
/>
<Button
v-if="!isSepValid"
variant="outline"
type="button"
class="bg-primary"
size="sm"
:disabled="isCheckingSep || isLoading || isReadonly"
@click="onAddSep"
>
<Icon
v-if="isCheckingSep"
name="i-lucide-loader-2"
class="h-4 w-4 animate-spin"
/>
<Icon
v-else
name="i-lucide-plus"
class="h-4 w-4"
/>
</Button>
<Button
v-else
variant="outline"
type="button"
class="bg-green-500 text-white hover:bg-green-600"
size="sm"
disabled
>
<Icon
name="i-lucide-check"
class="h-4 w-4"
/>
</Button>
</div>
</Field>
</Cell>
<FileUpload
field-name="sepFile"
label="Dokumen SEP"
placeholder="Unggah dokumen SEP"
:accept="['pdf', 'jpg', 'png']"
:max-size-mb="1"
/>
<FileUpload
field-name="sippFile"
label="Dokumen SIPP"
placeholder="Unggah dokumen SIPP"
:accept="['pdf', 'jpg', 'png']"
:max-size-mb="1"
/>
</Block>
</template>
</form>
</div>
</template>
+40 -16
View File
@@ -7,17 +7,37 @@ const props = defineProps<{
data: Encounter data: Encounter
}>() }>()
let address = ref('') const addressText = computed(() => {
if (props.data.patient.person.addresses) { if (props.data.patient.person.addresses && props.data.patient.person.addresses.length > 0) {
address.value = props.data.patient.person.addresses.map((a) => a.address).join(', ') return props.data.patient.person.addresses.map((a) => a.address).join(', ')
} }
return '-'
})
let dpjp = ref('') const paymentMethodText = computed(() => {
const code = props.data.paymentMethod_code
if (!code) return '-'
// Map payment method codes
if (code === 'insurance') {
return 'JKN'
} else if (code === 'jkn') {
return 'JKN'
} else if (code === 'jkmm') {
return 'JKMM'
} else if (code === 'spm') {
return 'SPM'
} else if (code === 'pks') {
return 'PKS'
}
})
let dpjpText = ref('')
if (props.data.responsible_doctor) { if (props.data.responsible_doctor) {
const dp = props.data.responsible_doctor.employee.person const dp = props.data.responsible_doctor.employee.person
dpjp.value = `${dp.frontTitle} ${dp.name} ${dp.endTitle}` dpjpText.value = `${dp.frontTitle} ${dp.name} ${dp.endTitle}`
} else if (props.data.appointment_doctor) { } else if (props.data.appointment_doctor) {
dpjp.value = props.data.appointment_doctor.employee.person.name dpjpText.value = props.data.appointment_doctor.employee.person.name
} }
</script> </script>
@@ -31,7 +51,7 @@ if (props.data.responsible_doctor) {
<div> <div>
<DE.Block mode="preview"> <DE.Block mode="preview">
<DE.Cell> <DE.Cell>
<DE.Label class="font-semibold">No. RM</DE.Label> <DE.Label class="font-semibold">Tgl. Lahir</DE.Label>
<DE.Colon /> <DE.Colon />
<DE.Field> <DE.Field>
{{ data.patient.person.birthDate?.substring(0, 10) }} {{ data.patient.person.birthDate?.substring(0, 10) }}
@@ -48,7 +68,7 @@ if (props.data.responsible_doctor) {
<DE.Label class="font-semibold">Alamat</DE.Label> <DE.Label class="font-semibold">Alamat</DE.Label>
<DE.Colon /> <DE.Colon />
<DE.Field> <DE.Field>
<div v-html="address"></div> {{ addressText }}
</DE.Field> </DE.Field>
</DE.Cell> </DE.Cell>
</DE.Block> </DE.Block>
@@ -70,7 +90,7 @@ if (props.data.responsible_doctor) {
</DE.Field> </DE.Field>
</DE.Cell> </DE.Cell>
<DE.Cell> <DE.Cell>
<DE.Label position="dynamic" class="font-semibold">Klinik</DE.Label> <DE.Label position="dynamic" class="font-semibold">Diagnosa</DE.Label>
<DE.Colon /> <DE.Colon />
<DE.Field> <DE.Field>
{{ data.unit?.name }} {{ data.unit?.name }}
@@ -84,17 +104,21 @@ if (props.data.responsible_doctor) {
<DE.Label position="dynamic" class="font-semibold">DPJP</DE.Label> <DE.Label position="dynamic" class="font-semibold">DPJP</DE.Label>
<DE.Colon /> <DE.Colon />
<DE.Field> <DE.Field>
{{ dpjp }} {{ dpjpText }}
</DE.Field> </DE.Field>
</DE.Cell> </DE.Cell>
<DE.Cell> <DE.Cell>
<DE.Label <DE.Label position="dynamic" class="font-semibold">Pembayaran</DE.Label>
position="dynamic" <DE.Colon />
class="!text-base font-semibold 2xl:!text-lg" <DE.Field>
> {{ paymentMethodText }}
</DE.Field>
</DE.Cell>
<DE.Cell>
<DE.Label position="dynamic" class="!text-base !font-semibold 2xl:!text-lg">
Billing Billing
</DE.Label> </DE.Label>
<DE.Colon class="pt-1" /> <DE.Colon class="pt-1"/>
<DE.Field class="text-base 2xl:text-lg"> <DE.Field class="text-base 2xl:text-lg">
Rp. 000.000 Rp. 000.000
<!-- {{ data }} --> <!-- {{ data }} -->
+38 -23
View File
@@ -8,8 +8,11 @@ import AppViewPatient from '~/components/app/patient/view-patient.vue'
import { refDebounced } from '@vueuse/core' import { refDebounced } from '@vueuse/core'
// Handlers // Handlers
import { getDetail as getDoctorDetail } from '~/services/doctor.service'
import { useEncounterEntry } from '~/handlers/encounter-entry.handler' import { useEncounterEntry } from '~/handlers/encounter-entry.handler'
import { genDoctor, type Doctor } from '~/models/doctor'
// Props
const props = defineProps<{ const props = defineProps<{
id: number id: number
classCode?: 'ambulatory' | 'emergency' | 'inpatient' | 'outpatient' classCode?: 'ambulatory' | 'emergency' | 'inpatient' | 'outpatient'
@@ -54,7 +57,33 @@ const {
} = useEncounterEntry(props) } = useEncounterEntry(props)
const debouncedSepNumber = refDebounced(sepNumber, 500) 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() { function handleSavePatient() {
selectedPatientObject.value = null selectedPatientObject.value = null
setTimeout(() => { setTimeout(() => {
@@ -100,29 +129,13 @@ async function handleEvent(menu: string, value?: any) {
} }
} }
provide('rec_select_id', recSelectId) async function getDoctorInfo(value: string) {
provide('table_data_loader', isLoading) const resp = await getDoctorDetail(value, { includes: 'unit,specialist,subspecialist'})
if (resp.success) {
watch(debouncedSepNumber, async (newValue) => { selectedDoctor.value = resp.body.data
await getValidateSepNumber(newValue) // console.log(selectedDoctor.value)
})
watch(
() => formObjects.value?.paymentType,
(newValue) => {
isSepValid.value = false
if (newValue !== 'jkn') {
sepNumber.value = ''
} }
}, }
)
onMounted(async () => {
await handleInit()
if (props.id > 0) {
await loadEncounterDetail()
}
})
</script> </script>
<template> <template>
@@ -144,9 +157,11 @@ onMounted(async () => {
:seps="sepsList" :seps="sepsList"
:participant-groups="participantGroupsList" :participant-groups="participantGroupsList"
:specialists="specialistsTree" :specialists="specialistsTree"
:doctor="doctorsList" :doctorItems="doctorsList"
:selectedDoctor="selectedDoctor"
:patient="selectedPatientObject" :patient="selectedPatientObject"
:objects="formObjects" :objects="formObjects"
@on-select-doctor="getDoctorInfo"
@event="handleEvent" @event="handleEvent"
@fetch="handleFetch" @fetch="handleFetch"
/> />
+14 -16
View File
@@ -70,18 +70,18 @@ export function useEncounterEntry(props: {
}) })
function getListPath(): string { function getListPath(): string {
if (props.classCode === 'ambulatory' && props.subClassCode === 'rehab') { if (props.classCode === 'ambulatory') {
return '/rehab/encounter' return '/ambulatory/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'
} }
// 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' return '/encounter'
} }
@@ -257,11 +257,10 @@ export function useEncounterEntry(props: {
async function handleFetchDoctors(subSpecialistId: string | null = null) { async function handleFetchDoctors(subSpecialistId: string | null = null) {
try { try {
const filterParams: any = { 'page-size': 100, includes: 'employee-Person' } const filterParams: any = { 'page-size': 100, includes: 'employee-Person,unit,specialist,subspecialist' }
if (!subSpecialistId) { if (!subSpecialistId) {
const doctors = await getDoctorValueLabelList(filterParams, true) doctorsList.value = await getDoctorValueLabelList(filterParams, true)
doctorsList.value = doctors
return return
} }
@@ -273,8 +272,7 @@ export function useEncounterEntry(props: {
filterParams['specialist-id'] = subSpecialistId filterParams['specialist-id'] = subSpecialistId
} }
const doctors = await getDoctorValueLabelList(filterParams, true) doctorsList.value = await getDoctorValueLabelList(filterParams, true)
doctorsList.value = doctors
} catch (error) { } catch (error) {
console.error('Error fetching doctors:', error) console.error('Error fetching doctors:', error)
doctorsList.value = [] doctorsList.value = []
+105 -57
View File
@@ -55,23 +55,29 @@ const defaultKeys: Record<string, any> = {
classCode: ['ambulatory', 'emergency', 'inpatient'], classCode: ['ambulatory', 'emergency', 'inpatient'],
unit: 'all', unit: 'all',
}, },
earlyNurseryAssessment: {
id: 'early-nursery-assessment',
title: 'Pengkajian Awal Keperawatan',
classCode: ['ambulatory', 'emergency', 'inpatient'],
unit: 'all',
},
earlyMedicalAssessment: { earlyMedicalAssessment: {
id: 'early-medical-assessment', id: 'early-medical-assessment',
title: 'Pengkajian Awal Medis', title: 'Pengkajian Awal Medis',
classCode: ['ambulatory', 'emergency', 'inpatient'], classCode: ['ambulatory', 'emergency', 'inpatient'],
unit: 'all', unit: 'all',
}, },
rehabMedicalAssessment: { earlyMedicalRehabAssessment: {
id: 'rehab-medical-assessment', id: 'rehab-medical-assessment',
title: 'Pengkajian Awal Medis Rehabilitasi Medis', title: 'Pengkajian Awal Medis Rehabilitasi Medis',
classCode: ['ambulatory', 'emergency', 'inpatient'], classCode: ['ambulatory'],
unit: 'rehab', unit: 'rehab',
afterId: 'early-medical-assessment', afterId: 'early-medical-assessment',
}, },
functionAssessment: { functionAssessment: {
id: 'function-assessment', id: 'function-assessment',
title: 'Asesmen Fungsi', title: 'Asesmen Fungsi',
classCode: ['ambulatory', 'emergency', 'inpatient'], classCode: ['ambulatory'],
unit: 'rehab', unit: 'rehab',
afterId: 'rehab-medical-assessment', afterId: 'rehab-medical-assessment',
}, },
@@ -102,16 +108,22 @@ const defaultKeys: Record<string, any> = {
classCode: ['ambulatory', 'emergency', 'inpatient'], classCode: ['ambulatory', 'emergency', 'inpatient'],
unit: 'all', unit: 'all',
}, },
consent: { generalConsent: {
id: 'consent', id: 'general-consent',
title: 'General Consent', title: 'General Consent',
classCode: ['ambulatory', 'emergency', 'inpatient'], classCode: ['ambulatory', 'emergency', 'inpatient'],
unit: 'all', unit: 'all',
}, },
patientNote: { patientAmbNote: {
id: 'patient-note', id: 'patient-amb-note',
title: 'CPRJ', title: 'CPRJ',
classCode: ['ambulatory', 'emergency', 'inpatient'], classCode: ['ambulatory', 'emergency'],
unit: 'all',
},
patientDevNote: {
id: 'patient-dev-note',
title: 'CPP',
classCode: ['inpatient'],
unit: 'all', unit: 'all',
}, },
prescription: { prescription: {
@@ -120,38 +132,38 @@ const defaultKeys: Record<string, any> = {
classCode: ['ambulatory', 'emergency', 'inpatient'], classCode: ['ambulatory', 'emergency', 'inpatient'],
unit: 'all', unit: 'all',
}, },
device: { deviceOrder: {
id: 'device-order', id: 'device-order',
title: 'Order Alkes', title: 'Order Alkes',
classCode: ['ambulatory', 'emergency', 'inpatient'], classCode: ['ambulatory', 'emergency', 'inpatient'],
unit: 'all', unit: 'all',
}, },
mcuRadiology: { radiologyOrder: {
id: 'mcu-radiology', id: 'radiology-order',
title: 'Order Radiologi', title: 'Order Radiologi',
classCode: ['ambulatory', 'emergency', 'inpatient'], classCode: ['ambulatory', 'emergency', 'inpatient'],
unit: 'all', unit: 'all',
}, },
mcuLabPc: { cpLabOrder: {
id: 'mcu-lab-pc', id: 'cp-lab-order',
title: 'Order Lab PK', title: 'Order Lab PK',
classCode: ['ambulatory', 'emergency', 'inpatient'], classCode: ['ambulatory', 'emergency', 'inpatient'],
unit: 'all', unit: 'all',
}, },
mcuLabMicro: { microLabOrder: {
id: 'mcu-lab-micro', id: 'micro-lab-order',
title: 'Order Lab Mikro', title: 'Order Lab Mikro',
classCode: ['ambulatory', 'emergency', 'inpatient'], classCode: ['ambulatory', 'emergency', 'inpatient'],
unit: 'all', unit: 'all',
}, },
mcuLabPa: { paLabOrder: {
id: 'mcu-lab-pa', id: 'pa-lab-order',
title: 'Order Lab PA', title: 'Order Lab PA',
classCode: ['ambulatory', 'emergency', 'inpatient'], classCode: ['ambulatory', 'emergency', 'inpatient'],
unit: 'all', unit: 'all',
}, },
medicalAction: { actionRoomOrder: {
id: 'medical-action', id: 'action-room-order',
title: 'Order Ruang Tindakan', title: 'Order Ruang Tindakan',
classCode: ['ambulatory', 'emergency', 'inpatient'], classCode: ['ambulatory', 'emergency', 'inpatient'],
unit: 'all', unit: 'all',
@@ -162,21 +174,45 @@ const defaultKeys: Record<string, any> = {
classCode: ['ambulatory', 'emergency', 'inpatient'], classCode: ['ambulatory', 'emergency', 'inpatient'],
unit: 'all', unit: 'all',
}, },
actionReport: {
id: 'action-report',
title: 'Laporan Tindakan',
classCode: ['ambulatory', 'emergency', 'inpatient'],
unit: 'all',
},
surgeryReport: {
id: 'surgery-report',
title: 'Laporan Operasi',
classCode: ['ambulatory', 'emergency', 'inpatient'],
unit: 'all',
},
vacsinData: {
id: 'vacsin-data',
title: 'Data Vaksin',
classCode: ['ambulatory', 'emergency', 'inpatient'],
unit: 'all',
},
consultation: { consultation: {
id: 'consultation', id: 'consultation',
title: 'Konsultasi', title: 'Konsultasi',
classCode: ['ambulatory', 'emergency', 'inpatient'], classCode: ['ambulatory', 'emergency', 'inpatient'],
unit: 'all', unit: 'all',
}, },
resume: { controlLetter: {
id: 'resume', id: 'control-letter',
title: 'Resume', title: 'Surat Kontrol',
classCode: ['ambulatory', 'emergency', 'inpatient'], classCode: ['ambulatory', 'emergency', 'inpatient'],
unit: 'all', unit: 'all',
}, },
control: { inpatientLetter: {
id: 'control', id: 'inpatient-letter',
title: 'Surat Kontrol', title: 'SPRI',
classCode: ['ambulatory', 'emergency'],
unit: 'all',
},
refBack: {
id: 'reference-back',
title: 'PRB',
classCode: ['ambulatory', 'emergency', 'inpatient'], classCode: ['ambulatory', 'emergency', 'inpatient'],
unit: 'all', unit: 'all',
}, },
@@ -190,7 +226,19 @@ const defaultKeys: Record<string, any> = {
id: 'supporting-document', id: 'supporting-document',
title: 'Upload Dokumen Pendukung', title: 'Upload Dokumen Pendukung',
classCode: ['ambulatory'], classCode: ['ambulatory'],
unit: 'rehab', unit: 'all',
},
resume: {
id: 'resume',
title: 'Resume',
classCode: ['ambulatory', 'emergency', 'inpatient'],
unit: 'all',
},
ambResume: {
id: 'amb-resume',
title: 'Resume Medis Rawat Jalan',
classCode: ['ambulatory', 'emergency', 'inpatient'],
unit: 'all',
}, },
priceList: { priceList: {
id: 'price-list', id: 'price-list',
@@ -244,12 +292,12 @@ export function injectComponents(id: string | number, data: EncounterListData, m
label: currentKeys.earlyMedicalAssessment['title'], label: currentKeys.earlyMedicalAssessment['title'],
} }
} }
if (currentKeys?.rehabMedicalAssessment) { if (currentKeys?.earlyMedicalRehabAssessment) {
currentKeys.rehabMedicalAssessment['component'] = EarlyMedicalRehabListAsync currentKeys.earlyMedicalRehabAssessment['component'] = EarlyMedicalRehabListAsync
currentKeys.rehabMedicalAssessment['props'] = { currentKeys.earlyMedicalRehabAssessment['props'] = {
encounter: data?.encounter, encounter: data?.encounter,
type: 'early-rehab', type: 'early-rehab',
label: currentKeys.rehabMedicalAssessment['title'], label: currentKeys.earlyMedicalRehabAssessment['title'],
} }
} }
if (currentKeys?.functionAssessment) { if (currentKeys?.functionAssessment) {
@@ -287,44 +335,44 @@ export function injectComponents(id: string | number, data: EncounterListData, m
currentKeys.educationAssessment['component'] = null currentKeys.educationAssessment['component'] = null
currentKeys.educationAssessment['props'] = { encounter_id: id } currentKeys.educationAssessment['props'] = { encounter_id: id }
} }
if (currentKeys?.consent) { if (currentKeys?.generalConsent) {
currentKeys.consent['component'] = GeneralConsentListAsync currentKeys.generalConsent['component'] = GeneralConsentListAsync
currentKeys.consent['props'] = { encounter_id: id } currentKeys.generalConsent['props'] = { encounter_id: id }
} }
if (currentKeys?.patientNote) { if (currentKeys?.patientAmbNote) {
currentKeys.patientNote['component'] = CprjAsync currentKeys.patientAmbNote['component'] = CprjAsync
currentKeys.patientNote['props'] = { encounter_id: id } currentKeys.patientAmbNote['props'] = { encounter_id: id }
} }
if (currentKeys?.prescription) { if (currentKeys?.prescription) {
currentKeys.prescription['component'] = PrescriptionAsync currentKeys.prescription['component'] = PrescriptionAsync
currentKeys.prescription['props'] = { encounter_id: id } currentKeys.prescription['props'] = { encounter_id: id }
} }
if (currentKeys?.device) { if (currentKeys?.deviceOrder) {
currentKeys.device['component'] = DeviceOrderAsync currentKeys.deviceOrder['component'] = DeviceOrderAsync
currentKeys.device['props'] = { encounter_id: id } currentKeys.deviceOrder['props'] = { encounter_id: id }
} }
if (currentKeys?.mcuRadiology) { if (currentKeys?.radiologyOrder) {
currentKeys.mcuRadiology['component'] = RadiologyAsync currentKeys.radiologyOrder['component'] = RadiologyAsync
currentKeys.mcuRadiology['props'] = { encounter_id: id } currentKeys.radiologyOrder['props'] = { encounter_id: id }
} }
if (currentKeys?.mcuLabPc) { if (currentKeys?.cpLabOrder) {
currentKeys.mcuLabPc['component'] = CpLabOrderAsync currentKeys.cpLabOrder['component'] = CpLabOrderAsync
currentKeys.mcuLabPc['props'] = { encounter_id: id } currentKeys.cpLabOrder['props'] = { encounter_id: id }
} }
if (currentKeys?.mcuLabMicro) { if (currentKeys?.microLabOrder) {
// TODO: add component for mcuLabMicro // TODO: add component for microLabOrder
currentKeys.mcuLabMicro['component'] = null currentKeys.microLabOrder['component'] = null
currentKeys.mcuLabMicro['props'] = { encounter_id: id } currentKeys.microLabOrder['props'] = { encounter_id: id }
} }
if (currentKeys?.mcuLabPa) { if (currentKeys?.paLabOrder) {
// TODO: add component for mcuLabPa // TODO: add component for paLabOrder
currentKeys.mcuLabPa['component'] = null currentKeys.paLabOrder['component'] = null
currentKeys.mcuLabPa['props'] = { encounter_id: id } currentKeys.paLabOrder['props'] = { encounter_id: id }
} }
if (currentKeys?.medicalAction) { if (currentKeys?.actionRoomOrder) {
// TODO: add component for medicalAction // TODO: add component for actionRoomOrder
currentKeys.medicalAction['component'] = null currentKeys.actionRoomOrder['component'] = null
currentKeys.medicalAction['props'] = { encounter_id: id } currentKeys.actionRoomOrder['props'] = { encounter_id: id }
} }
if (currentKeys?.mcuResult) { if (currentKeys?.mcuResult) {
// TODO: add component for mcuResult // TODO: add component for mcuResult
+5 -3
View File
@@ -1,5 +1,6 @@
import { type Base, genBase } from "./_base" import { type Base, genBase } from "./_base"
import { type Employee, genEmployee } from "./employee" import { type Employee, genEmployee } from "./employee"
import type { Unit } from "./unit"
import type { Specialist } from "./specialist" import type { Specialist } from "./specialist"
import type { Subspecialist } from "./subspecialist" import type { Subspecialist } from "./subspecialist"
@@ -9,10 +10,11 @@ export interface Doctor extends Base {
ihs_number: string ihs_number: string
sip_number: string sip_number: string
code?: string code?: string
unit_icode?: number unit_code?: string
specialist_icode?: number unit?: Unit
specialist_code?: string
specialist?: Specialist specialist?: Specialist
subspecialist_icode?: number subspecialist_code?: string
subspecialist?: Subspecialist subspecialist?: Subspecialist
bpjs_code?: string bpjs_code?: string
} }
+10 -7
View File
@@ -3,6 +3,7 @@ import { type Doctor, genDoctor } from "./doctor"
import { genEmployee, type Employee } from "./employee" import { genEmployee, type Employee } from "./employee"
import type { EncounterDocument } from "./encounter-document" import type { EncounterDocument } from "./encounter-document"
import type { InternalReference } from "./internal-reference" import type { InternalReference } from "./internal-reference"
import type { Nurse } from "./nurse"
import { type Patient, genPatient } from "./patient" import { type Patient, genPatient } from "./patient"
import type { Specialist } from "./specialist" import type { Specialist } from "./specialist"
import type { Subspecialist } from "./subspecialist" import type { Subspecialist } from "./subspecialist"
@@ -14,18 +15,20 @@ export interface Encounter {
patient: Patient patient: Patient
registeredAt: string registeredAt: string
class_code: string class_code: string
unit_id: number unit_code: string
unit: Unit unit: Unit
specialist_id?: number specialist_code?: string
specilist?: Specialist specilist?: Specialist
subspecialist_id?: number subspecialist_code?: string
subspecialist?: Subspecialist subspecialist?: Subspecialist
visitDate: string visitDate: string
adm_employee_id: number adm_employee_id: number
adm_employee: Employee adm_employee: Employee
appointment_doctor_id: number responsible_nurse_code?: string
responsible_nurse?: Nurse
appointment_doctor_code: string
appointment_doctor: Doctor appointment_doctor: Doctor
responsible_doctor_id?: number responsible_doctor_code?: string
responsible_doctor?: Doctor responsible_doctor?: Doctor
refSource_name?: string refSource_name?: string
appointment_id?: number appointment_id?: number
@@ -49,12 +52,12 @@ export function genEncounter(): Encounter {
patient: genPatient(), patient: genPatient(),
registeredAt: '', registeredAt: '',
class_code: '', class_code: '',
unit_id: 0, unit_code: 0,
unit: genUnit(), unit: genUnit(),
visitDate: '', visitDate: '',
adm_employee_id: 0, adm_employee_id: 0,
adm_employee: genEmployee(), adm_employee: genEmployee(),
appointment_doctor_id: 0, appointment_doctor_code: '',
appointment_doctor: genDoctor(), appointment_doctor: genDoctor(),
medicalDischargeEducation: '', medicalDischargeEducation: '',
status_code: '', status_code: '',
+18 -16
View File
@@ -2,10 +2,12 @@ import { z } from 'zod'
const ERROR_MESSAGES = { const ERROR_MESSAGES = {
required: { required: {
doctorId: 'Dokter wajib diisi', doctor_code: 'Dokter wajib diisi',
registerDate: 'Tanggal Daftar wajib diisi', registerDate: 'Tanggal Daftar wajib diisi',
paymentType: 'Jenis Pembayaran wajib diisi', paymentMethod_code: 'Jenis Pembayaran wajib diisi',
subSpecialistId: 'Subspesialis wajib diisi', unit_code: 'Spesialis wajib diisi',
// specialist_code: 'Spesialis wajib diisi',
// subSpecialist_code: 'Subspesialis wajib diisi',
patientCategory: 'Kelompok Peserta wajib diisi', patientCategory: 'Kelompok Peserta wajib diisi',
cardNumber: 'No. Kartu BPJS wajib diisi', cardNumber: 'No. Kartu BPJS wajib diisi',
sepType: 'Jenis SEP wajib diisi', sepType: 'Jenis SEP wajib diisi',
@@ -23,19 +25,19 @@ const IntegrationEncounterSchema = z
medicalRecordNumber: z.string().optional(), medicalRecordNumber: z.string().optional(),
// Visit data // Visit data
doctorId: z doctor_code: z
.string({ required_error: ERROR_MESSAGES.required.doctorId }) .string({ required_error: ERROR_MESSAGES.required.doctor_code })
.min(1, ERROR_MESSAGES.required.doctorId), .min(1, ERROR_MESSAGES.required.doctor_code),
subSpecialistId: z unit_code: z
.string({ required_error: ERROR_MESSAGES.required.subSpecialistId }) .string({ required_error: ERROR_MESSAGES.required.unit_code })
.min(1, ERROR_MESSAGES.required.subSpecialistId) .min(1, ERROR_MESSAGES.required.unit_code)
.optional(), .optional(),
registerDate: z registerDate: z
.string({ required_error: ERROR_MESSAGES.required.registerDate }) .string({ required_error: ERROR_MESSAGES.required.registerDate })
.min(1, ERROR_MESSAGES.required.registerDate), .min(1, ERROR_MESSAGES.required.registerDate),
paymentType: z paymentMethod_code: z
.string({ required_error: ERROR_MESSAGES.required.paymentType }) .string({ required_error: ERROR_MESSAGES.required.paymentMethod_code })
.min(1, ERROR_MESSAGES.required.paymentType), .min(1, ERROR_MESSAGES.required.paymentMethod_code),
// BPJS related fields // BPJS related fields
patientCategory: z patientCategory: z
@@ -76,7 +78,7 @@ const IntegrationEncounterSchema = z
.refine( .refine(
(data) => { (data) => {
// If payment type is jkn, then patient category is required // If payment type is jkn, then patient category is required
if (data.paymentType === 'jkn') { if (data.paymentMethod_code === 'jkn') {
return data.patientCategory && data.patientCategory.trim() !== '' return data.patientCategory && data.patientCategory.trim() !== ''
} }
return true return true
@@ -89,7 +91,7 @@ const IntegrationEncounterSchema = z
.refine( .refine(
(data) => { (data) => {
// If payment type is jkn, then card number is required // If payment type is jkn, then card number is required
if (data.paymentType === 'jkn') { if (data.paymentMethod_code === 'jkn') {
return data.cardNumber && data.cardNumber.trim() !== '' return data.cardNumber && data.cardNumber.trim() !== ''
} }
return true return true
@@ -102,7 +104,7 @@ const IntegrationEncounterSchema = z
.refine( .refine(
(data) => { (data) => {
// If payment type is jkn, then SEP type is required // If payment type is jkn, then SEP type is required
if (data.paymentType === 'jkn') { if (data.paymentMethod_code === 'jkn') {
return data.sepType && data.sepType.trim() !== '' return data.sepType && data.sepType.trim() !== ''
} }
return true return true
@@ -115,7 +117,7 @@ const IntegrationEncounterSchema = z
.refine( .refine(
(data) => { (data) => {
// If payment type is jkn and SEP type is selected, then SEP number is required // If payment type is jkn and SEP type is selected, then SEP number is required
if (data.paymentType === 'jkn' && data.sepType && data.sepType.trim() !== '') { if (data.paymentMethod_code === 'jkn' && data.sepType && data.sepType.trim() !== '') {
return data.sepNumber && data.sepNumber.trim() !== '' return data.sepNumber && data.sepNumber.trim() !== ''
} }
return true return true
+2 -2
View File
@@ -13,8 +13,8 @@ export function getList(params: any = null) {
return base.getList(path, params, name) return base.getList(path, params, name)
} }
export function getDetail(id: number | string) { export function getDetail(id: number | string, params?: any) {
return base.getDetail(path, id, name) return base.getDetail(path, id, name, params)
} }
export function update(id: number | string, data: any) { export function update(id: number | string, data: any) {