Merge pull request #224 from dikstub-rssa/feat/adj-enc-list-199

Feat: Adjust Encounter
This commit is contained in:
Munawwirul Jamal
2025-12-09 18:19:15 +07:00
committed by GitHub
15 changed files with 647 additions and 170 deletions
@@ -43,7 +43,7 @@ const emit = defineEmits<{
}>() }>()
// Validation schema // Validation schema
const { handleSubmit, errors, defineField, meta } = useForm<IntegrationEncounterFormData>({ const { handleSubmit, errors, defineField, meta } = useForm<any>({
validationSchema: toTypedSchema(IntegrationEncounterSchema), validationSchema: toTypedSchema(IntegrationEncounterSchema),
}) })
@@ -141,7 +141,7 @@ function onAddSep() {
registerDate: registerDate.value, registerDate: registerDate.value,
cardNumber: cardNumber.value, cardNumber: cardNumber.value,
paymentType: paymentType.value, paymentType: paymentType.value,
sepType: sepType.value sepType: sepType.value,
} }
emit('event', 'add-sep', formValues) emit('event', 'add-sep', formValues)
} }
@@ -454,7 +454,7 @@ defineExpose({
name="i-lucide-loader-2" name="i-lucide-loader-2"
class="h-4 w-4 animate-spin" class="h-4 w-4 animate-spin"
/> />
<Icon <Icon
v-else v-else
name="i-lucide-plus" name="i-lucide-plus"
class="h-4 w-4" class="h-4 w-4"
@@ -187,6 +187,7 @@ function onAddSep() {
registerDate: registerDate.value, registerDate: registerDate.value,
cardNumber: cardNumber.value, cardNumber: cardNumber.value,
paymentMethodCode: paymentMethodCode.value, paymentMethodCode: paymentMethodCode.value,
unitCode: props.selectedDoctor?.unit?.code || '',
sepFile: sepFile.value, sepFile: sepFile.value,
sippFile: sippFile.value, sippFile: sippFile.value,
sepType: sepType.value, sepType: sepType.value,
@@ -0,0 +1,114 @@
<script setup lang="ts">
import type { FormErrors } from '~/types/error'
import { toTypedSchema } from '@vee-validate/zod'
import FieldGroup from '~/components/pub/my-ui/form/field-group.vue'
import Field from '~/components/pub/my-ui/form/field.vue'
import Label from '~/components/pub/my-ui/form/label.vue'
import Select from '~/components/pub/my-ui/form/select.vue'
import { Form } from '~/components/pub/ui/form'
interface InstallationFormData {
name: string
code: string
encounterClassCode: string
}
const props = defineProps<{
installation: {
msg: {
placeholder: string
}
items: {
value: string
label: string
code: string
}[]
}
schema: any
initialValues?: Partial<InstallationFormData>
errors?: FormErrors
}>()
const emit = defineEmits<{
submit: [values: InstallationFormData, resetForm: () => void]
cancel: [resetForm: () => void]
}>()
const formSchema = toTypedSchema(props.schema)
// Form submission handler
function onSubmitForm(values: any, { resetForm }: { resetForm: () => void }) {
const formData: InstallationFormData = {
name: values.name || '',
code: values.code || '',
encounterClassCode: values.encounterClassCode || '',
}
emit('submit', formData, resetForm)
}
// Form cancel handler
function onCancelForm({ resetForm }: { resetForm: () => void }) {
emit('cancel', resetForm)
}
const items = ref([
{ label: 'Rujukan Internal', value: 'ri' },
{ label: 'SEP Rujukan', value: 'sr' },
])
</script>
<template>
<Form
v-slot="{ handleSubmit, resetForm }"
as=""
keep-values
:validation-schema="formSchema"
:initial-values="initialValues"
>
<form id="entry-form" @submit="handleSubmit($event, (values) => onSubmitForm(values, { resetForm }))">
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg xl:text-xl">
<div class="flex flex-col justify-between">
<FieldGroup>
<Label label-for="parentId">Cara Bayar</Label>
<Field id="encounterClassCode" :errors="errors">
<FormField v-slot="{ componentField }" name="encounterClassCode">
<FormItem>
<FormControl>
<Select v-bind="componentField" :items="items" />
</FormControl>
<FormMessage />
</FormItem>
</FormField>
</Field>
</FieldGroup>
<FieldGroup>
<Label label-for="parentId">Poliklinik</Label>
<Field id="encounterClassCode" :errors="errors">
<FormField v-slot="{ componentField }" name="encounterClassCode">
<FormItem>
<FormControl>
<Select v-bind="componentField" :items="items" />
</FormControl>
<FormMessage />
</FormItem>
</FormField>
</Field>
</FieldGroup>
<FieldGroup>
<Label label-for="parentId">Kunjungan</Label>
<Field id="encounterClassCode" :errors="errors">
<FormField v-slot="{ componentField }" name="encounterClassCode">
<FormItem>
<FormControl>
<Select v-bind="componentField" :items="items" />
</FormControl>
<FormMessage />
</FormItem>
</FormField>
</Field>
</FieldGroup>
</div>
</div>
</form>
</Form>
</template>
+93 -98
View File
@@ -1,114 +1,109 @@
<script setup lang="ts"> <script setup lang="ts">
import type { FormErrors } from '~/types/error' // Components
import { toTypedSchema } from '@vee-validate/zod' import * as DE from '~/components/pub/my-ui/doc-entry'
import FieldGroup from '~/components/pub/my-ui/form/field-group.vue'
import Field from '~/components/pub/my-ui/form/field.vue'
import Label from '~/components/pub/my-ui/form/label.vue'
import Select from '~/components/pub/my-ui/form/select.vue'
import { Form } from '~/components/pub/ui/form'
interface InstallationFormData {
name: string
code: string
encounterClassCode: string
}
// Props
const props = defineProps<{ const props = defineProps<{
installation: { payments: any[]
msg: { units: any[]
placeholder: string visits: any[]
}
items: {
value: string
label: string
code: string
}[]
}
schema: any
initialValues?: Partial<InstallationFormData>
errors?: FormErrors
}>() }>()
const emit = defineEmits<{ const emit = defineEmits<{
submit: [values: InstallationFormData, resetForm: () => void] (e: 'search', values: any): void
cancel: [resetForm: () => void] (e: 'reset'): void
}>() }>()
const formSchema = toTypedSchema(props.schema) const paymentItem = ref<any>(null)
const unitItem = ref<any>(null)
const visitItem = ref<any>(null)
// Form submission handler function handleReset() {
function onSubmitForm(values: any, { resetForm }: { resetForm: () => void }) { paymentItem.value = null
const formData: InstallationFormData = { unitItem.value = null
name: values.name || '', visitItem.value = null
code: values.code || '', emit('reset')
encounterClassCode: values.encounterClassCode || '',
}
emit('submit', formData, resetForm)
} }
// Form cancel handler function handleSearch() {
function onCancelForm({ resetForm }: { resetForm: () => void }) { emit('search', {
emit('cancel', resetForm) 'paymentMethod-code': paymentItem.value,
'unit-code': unitItem.value,
visit: visitItem.value,
})
} }
const items = ref([
{ label: 'Rujukan Internal', value: 'ri' },
{ label: 'SEP Rujukan', value: 'sr' },
])
</script> </script>
<template> <template>
<Form <div class="mx-auto w-full">
v-slot="{ handleSubmit, resetForm }" <DE.Block
as="" labelSize="thin"
keep-values class="!pt-0"
:validation-schema="formSchema" :colCount="1"
:initial-values="initialValues" :cellFlex="false"
> >
<form id="entry-form" @submit="handleSubmit($event, (values) => onSubmitForm(values, { resetForm }))"> <DE.Cell>
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg xl:text-xl"> <DE.Label height="compact">Cara Bayar</DE.Label>
<div class="flex flex-col justify-between"> <DE.Field>
<FieldGroup> <Select
<Label label-for="parentId">Cara Bayar</Label> id="paymentMethodCode"
<Field id="encounterClassCode" :errors="errors"> v-model="paymentItem"
<FormField v-slot="{ componentField }" name="encounterClassCode"> :items="payments || []"
<FormItem> placeholder="Pilih Jenis Pembayaran"
<FormControl> />
<Select v-bind="componentField" :items="items" /> </DE.Field>
</FormControl> </DE.Cell>
<FormMessage />
</FormItem> <DE.Cell>
</FormField> <DE.Label height="compact">Poliklinik</DE.Label>
</Field> <DE.Field>
</FieldGroup> <Select
<FieldGroup> id="unit"
<Label label-for="parentId">Poliklinik</Label> v-model="unitItem"
<Field id="encounterClassCode" :errors="errors"> :items="units || []"
<FormField v-slot="{ componentField }" name="encounterClassCode"> placeholder="Pilih Poliklinik"
<FormItem> />
<FormControl> </DE.Field>
<Select v-bind="componentField" :items="items" /> </DE.Cell>
</FormControl>
<FormMessage /> <DE.Cell>
</FormItem> <DE.Label height="compact">Kunjungan</DE.Label>
</FormField> <DE.Field>
</Field> <Select
</FieldGroup> id="visit"
<FieldGroup> v-model="visitItem"
<Label label-for="parentId">Kunjungan</Label> :items="visits || []"
<Field id="encounterClassCode" :errors="errors"> placeholder="Pilih Kunjungan"
<FormField v-slot="{ componentField }" name="encounterClassCode"> />
<FormItem> </DE.Field>
<FormControl> </DE.Cell>
<Select v-bind="componentField" :items="items" /> </DE.Block>
</FormControl>
<FormMessage /> <div class="flex w-full justify-end gap-2">
</FormItem> <Button
</FormField> variant="outline"
</Field> type="button"
</FieldGroup> class="h-[40px] rounded-md border-orange-400 text-orange-400 hover:bg-green-50"
</div> @click="handleReset"
</div> >
</form> <Icon
</Form> name="i-lucide-rotate-ccw"
class="h-5 w-5"
/>
Reset
</Button>
<Button
variant="default"
type="button"
class="h-[40px]"
@click="handleSearch"
>
<Icon
name="i-lucide-check"
class="h-5 w-5"
/>
Terapkan
</Button>
</div>
</div>
</template> </template>
+30 -12
View File
@@ -20,11 +20,13 @@ const props = defineProps<{
const emit = defineEmits<{ const emit = defineEmits<{
apply: [filters: { personName: string; startDate: string; endDate: string }] apply: [filters: { personName: string; startDate: string; endDate: string }]
click: []
}>() }>()
const searchQuery = ref('') const searchQuery = ref('')
const isRoleRegistration = props.activePositon === 'registration' const isRoleRegistration = props.activePositon === 'registration'
const isRoleMedical = props.activePositon === 'medical' const isRoleMedical = props.activePositon === 'medical'
const debouncedSearchQuery = refDebounced(searchQuery, 500)
const df = new DateFormatter('en-US', { const df = new DateFormatter('en-US', {
dateStyle: 'medium', dateStyle: 'medium',
@@ -43,14 +45,18 @@ const oneMonthAgoCalendar = new CalendarDate(
oneMonthAgo.getDate(), oneMonthAgo.getDate(),
) )
const value = ref({ const dateRange = ref({
start: oneMonthAgoCalendar, start: oneMonthAgoCalendar,
end: todayCalendar, end: todayCalendar,
}) as Ref<DateRange> }) as Ref<DateRange>
function onFilterClick() { function onFilterClick() {
const startDate = value.value.start ? value.value.start.toString() : '' emit('click')
const endDate = value.value.end ? value.value.end.toString() : startDate }
function onFilterApply() {
const startDate = dateRange.value.start ? dateRange.value.start.toString() : ''
const endDate = dateRange.value.end ? dateRange.value.end.toString() : startDate
emit('apply', { emit('apply', {
personName: searchQuery.value, personName: searchQuery.value,
@@ -58,6 +64,18 @@ function onFilterClick() {
endDate, endDate,
}) })
} }
watch(debouncedSearchQuery, () => {
onFilterApply()
})
watch(
dateRange,
() => {
onFilterApply()
},
{ deep: true },
)
</script> </script>
<template> <template>
@@ -76,18 +94,18 @@ function onFilterClick() {
<Button <Button
variant="outline" variant="outline"
:class=" :class="
cn('min-w-[240px] max-w-[320px] justify-start text-left font-normal', !value && 'text-muted-foreground') cn('min-w-[240px] max-w-[320px] justify-start text-left font-normal', !dateRange && 'text-muted-foreground')
" "
> >
<CalendarIcon class="mr-2 h-4 w-4" /> <CalendarIcon class="mr-2 h-4 w-4" />
<template v-if="value.start"> <template v-if="dateRange.start">
<template v-if="value.end"> <template v-if="dateRange.end">
{{ df.format(value.start.toDate(getLocalTimeZone())) }} - {{ df.format(dateRange.start.toDate(getLocalTimeZone())) }} -
{{ df.format(value.end.toDate(getLocalTimeZone())) }} {{ df.format(dateRange.end.toDate(getLocalTimeZone())) }}
</template> </template>
<template v-else> <template v-else>
{{ df.format(value.start.toDate(getLocalTimeZone())) }} {{ df.format(dateRange.start.toDate(getLocalTimeZone())) }}
</template> </template>
</template> </template>
<template v-else>Pick a date</template> <template v-else>Pick a date</template>
@@ -95,10 +113,10 @@ function onFilterClick() {
</PopoverTrigger> </PopoverTrigger>
<PopoverContent class="w-auto p-0"> <PopoverContent class="w-auto p-0">
<RangeCalendar <RangeCalendar
v-model="value" v-model="dateRange"
initial-focus initial-focus
:number-of-months="2" :number-of-months="2"
@update:start-value="(startDate) => (value.start = startDate)" @update:start-value="(startDate) => (dateRange.start = startDate)"
/> />
</PopoverContent> </PopoverContent>
</Popover> </Popover>
@@ -112,7 +130,7 @@ function onFilterClick() {
Filter Filter
</Button> </Button>
<DropdownMenu v-show="props.enableExport && (isRoleRegistration || isRoleMedical)"> <DropdownMenu v-if="props.enableExport && (isRoleRegistration || isRoleMedical)">
<DropdownMenuTrigger as-child> <DropdownMenuTrigger as-child>
<Button <Button
variant="outline" variant="outline"
+143 -14
View File
@@ -1,25 +1,19 @@
import type { Config, RecComponent } from '~/components/pub/my-ui/data-table' import type { Config, RecComponent } from '~/components/pub/my-ui/data-table'
import { defineAsyncComponent } from 'vue' import { defineAsyncComponent } from 'vue'
import type { Encounter } from '~/models/encounter' import type { Encounter } from '~/models/encounter'
import { educationCodes, genderCodes } from '~/lib/constants' import { formatAddress } from '~/models/person-address'
import { educationCodes, encounterClassCodes, genderCodes } from '~/lib/constants'
import { getAge } from '~/lib/date' import { getAge } from '~/lib/date'
type SmallDetailDto = Encounter type SmallDetailDto = Encounter
const action = defineAsyncComponent(() => import('./dropdown-action.vue')) const action = defineAsyncComponent(() => import('./dropdown-action.vue'))
const statusBadge = defineAsyncComponent(() => import('./status-badge.vue')) const statusBadge = defineAsyncComponent(() => import('./status-badge.vue'))
const vclaimSepInfo = defineAsyncComponent(() => import('./vclaim-sep-info.vue'))
const vclaimSepNone = defineAsyncComponent(() => import('./vclaim-sep-none.vue'))
export const config: Config = { export const defaultConfig: Config = {
cols: [ cols: [{}, {}, {}, { width: 160 }, {}, { width: 70 }, {}, { width: 50 }],
{},
{},
{},
{ width: 160 },
{},
{ width: 70 },
{ },
{ width: 50 },
],
headers: [ headers: [
[ [
@@ -94,11 +88,146 @@ export const config: Config = {
birth_date: (rec: unknown): unknown => { birth_date: (rec: unknown): unknown => {
const recX = rec as Encounter const recX = rec as Encounter
if (recX.patient?.person?.birthDate) { if (recX.patient?.person?.birthDate) {
return '' + return (
'<div>' + (recX.patient.person.birthDate as string).substring(0, 10) + ' / </div>' + '' +
'<div>' +
(recX.patient.person.birthDate as string).substring(0, 10) +
' / </div>' +
getAge(recX.patient.person.birthDate as string).extFormat getAge(recX.patient.person.birthDate as string).extFormat
)
} }
return '-' return '-'
}, },
}, },
} }
export const ambulatoryConfig: Config = {
cols: [{}, {}, {}, { width: 160 }, {}, { width: 70 }, {}, {}, {}, {}, {}, {}, { width: 50 }],
headers: [
[
{ label: 'TANGGAL' },
{ label: 'NO. RM' },
{ label: 'NO. BILL' },
{ label: 'NAMA PASIEN' },
{ label: 'L/P' },
{ label: 'ALAMAT' },
{ label: 'KLINIK' },
{ label: 'CARA BAYAR' },
{ label: 'RUJUKAN' },
{ label: 'KET. RUJUKAN' },
{ label: 'ASAL' },
{ label: 'SEP' },
{ label: 'STATUS', classVal: '!text-center' },
{ label: '' },
],
],
keys: [
'registeredAt',
'patientNumber',
'trxNumber',
'patient.person.name',
'gender',
'address',
'clinic',
'paymentMethod_code',
'referral',
'note',
'class_code',
'sep',
'status',
'action',
],
delKeyNames: [
{ key: 'code', label: 'Kode' },
{ key: 'name', label: 'Nama' },
],
parses: {
registeredAt: (rec: unknown): unknown => {
const recX = rec as Encounter
const currentDate = recX.registeredAt ? (recX.registeredAt as string) : (recX as any).createdAt
return currentDate ? currentDate.substring(0, 10) : '-'
},
patientNumber: (rec: unknown): unknown => {
const recX = rec as any
return recX.patient?.number || '-'
},
trxNumber: (rec: unknown): unknown => {
const recX = rec as any
return recX.trx_number || '-'
},
gender: (rec: unknown): unknown => {
const recX = rec as Encounter
if (recX.patient?.person?.gender_code) {
return genderCodes[recX.patient.person.gender_code]
}
return '-'
},
address: (rec: unknown): unknown => {
const recX = rec as Encounter
const addresses = recX.patient?.person?.addresses || []
const resident = addresses?.find((addr) => addr.locationType_code === 'domicile')
const text = resident ? formatAddress(resident) : '-'
return text.length > 20 ? text.substring(0, 17) + '...' : text
},
clinic: (rec: unknown): unknown => {
const recX = rec as Encounter
return recX.unit?.name || recX.refSource_name || '-'
},
paymentMethod_code: (rec: unknown): unknown => {
const recX = rec as Encounter
return (recX.paymentMethod_code || '-').toUpperCase()
},
referral: (rec: unknown): unknown => {
const recX = rec as any
return recX?.vclaimReference?.poliRujukan || '-'
},
note: (rec: unknown): unknown => {
const recX = rec as any
return recX?.vclaimReference?.ppkDirujuk || '-'
},
class_code: (rec: unknown): unknown => {
const recX = rec as Encounter
return recX.class_code ? encounterClassCodes[recX.class_code] : '-'
},
},
components: {
sep(rec: any) {
if (rec?.paymentMethod_code !== 'jkn') {
return {
rec: rec as object,
component: vclaimSepNone,
} as RecComponent
}
const res: RecComponent = {
rec: rec as object,
component: vclaimSepInfo,
}
return res
},
status(rec, idx) {
const recX = rec as Encounter
if (!recX.status_code) {
recX.status_code = 'new'
}
const res: RecComponent = {
idx,
rec: recX,
component: statusBadge,
}
return res
},
action(rec, idx) {
const res: RecComponent = {
idx,
rec: rec as object,
component: action,
}
return res
},
},
}
+4 -3
View File
@@ -1,15 +1,16 @@
<script setup lang="ts"> <script setup lang="ts">
import DataTable from '~/components/pub/my-ui/data-table/data-table.vue' import DataTable from '~/components/pub/my-ui/data-table/data-table.vue'
import { config } from './list.cfg' import { defaultConfig, ambulatoryConfig } from './list.cfg'
const props = defineProps<{ const props = defineProps<{
data: any[], data: any[]
classCode: string | undefined
}>() }>()
</script> </script>
<template> <template>
<DataTable <DataTable
v-bind="config" v-bind="props.classCode === 'ambulatory' ? ambulatoryConfig : defaultConfig"
:rows="props.data" :rows="props.data"
/> />
</template> </template>
@@ -0,0 +1,77 @@
<script setup lang="ts">
const props = defineProps<{
rec: any
}>()
const recSepId = inject<Ref<number>>('rec_sep_id')!
const recSepMenu = inject<Ref<string>>('rec_sep_menu')!
const recSepSubMenu = inject<Ref<string>>('rec_sep_sub_menu')!
const sepFileReview = ref<any>({})
const sippFileReview = ref<any>({})
const getEncounterDocument = () => {
const encounter = props.rec
sippFileReview.value = {}
sepFileReview.value = {}
if (encounter.encounterDocuments && Array.isArray(encounter.encounterDocuments)) {
for (const doc of encounter.encounterDocuments) {
if (doc.type_code === 'vclaim-sep') {
sepFileReview.value = { id: doc.id, fileName: doc.fileName, filePath: doc.filePath, type: doc.type_code }
} else if (doc.type_code === 'vclaim-sipp') {
sippFileReview.value = { id: doc.id, fileName: doc.fileName, filePath: doc.filePath, type: doc.type_code }
}
}
}
}
function handleSepView(menu: string, subMenu: string) {
recSepId.value = props.rec.id || 0
recSepMenu.value = menu
recSepSubMenu.value = subMenu
}
onMounted(() => {
getEncounterDocument()
})
</script>
<template>
<div class="flex flex-col gap-2">
<Button
variant="outline"
type="button"
class="h-[40px] rounded-md border-orange-400 text-orange-400 hover:bg-green-50"
@click="handleSepView('sipp', sippFileReview.id ? 'view' : 'edit')"
>
<Icon
v-if="sippFileReview.id"
name="i-lucide-eye"
class="h-5 w-5"
/>
<Icon
v-else
name="i-lucide-upload"
class="h-5 w-5"
/>
{{ sippFileReview.id ? 'Lihat SIPP' : 'Upload File SIPP' }}
</Button>
<Button
variant="outline"
type="button"
class="h-[40px] rounded-md border-orange-400 text-orange-400 hover:bg-green-50"
@click="handleSepView('sep', sepFileReview.id ? 'view' : 'edit')"
>
<Icon
v-if="sepFileReview.id"
name="i-lucide-eye"
class="h-5 w-5"
/>
<Icon
v-else
name="i-lucide-upload"
class="h-5 w-5"
/>
{{ sepFileReview.id ? 'Lihat SEP' : 'Upload File SEP' }}
</Button>
</div>
</template>
@@ -0,0 +1,11 @@
<script setup lang="ts">
const props = defineProps<{
rec: any
}>()
</script>
<template>
<div class="flex flex-col gap-2">
<span>Tidak ada</span>
</div>
</template>
+159 -32
View File
@@ -7,8 +7,12 @@ import { ActionEvents } from '~/components/pub/my-ui/data/types'
import Dialog from '~/components/pub/my-ui/modal/dialog.vue' import Dialog from '~/components/pub/my-ui/modal/dialog.vue'
import * as CH from '~/components/pub/my-ui/content-header' import * as CH from '~/components/pub/my-ui/content-header'
import RecordConfirmation from '~/components/pub/my-ui/confirmation/record-confirmation.vue' import RecordConfirmation from '~/components/pub/my-ui/confirmation/record-confirmation.vue'
import FileUpload from '~/components/pub/my-ui/form/file-field.vue'
import { useSidebar } from '~/components/pub/ui/sidebar/utils' import { useSidebar } from '~/components/pub/ui/sidebar/utils'
// Constants
import { paymentTypes, sepRefTypeCodes } from '~/lib/constants.vclaim'
// App libs // App libs
import { getServicePosition } from '~/lib/roles' // previously getPositionAs import { getServicePosition } from '~/lib/roles' // previously getPositionAs
@@ -18,6 +22,10 @@ import {
remove as removeEncounter, remove as removeEncounter,
cancel as cancelEncounter, cancel as cancelEncounter,
} from '~/services/encounter.service' } from '~/services/encounter.service'
import { getValueLabelList as getUnits } from '~/services/unit.service'
// Handlers
import { uploadAttachmentCustom } from '~/handlers/supporting-document.handler'
// Apps // Apps
import Content from '~/components/app/encounter/list.vue' import Content from '~/components/app/encounter/list.vue'
@@ -53,9 +61,16 @@ const isLoading = reactive<DataTableLoader>({
const recId = ref<number>(0) const recId = ref<number>(0)
const recAction = ref<string>('') const recAction = ref<string>('')
const recItem = ref<any>(null) const recItem = ref<any>(null)
const recSepId = ref<number>(0)
const recSepMenu = ref<string>('')
const recSepSubMenu = ref<string>('')
const isFilterFormDialogOpen = ref(false) const isFilterFormDialogOpen = ref(false)
const isRecordConfirmationOpen = ref(false) const isRecordConfirmationOpen = ref(false)
const isRecordCancelOpen = ref(false) const isRecordCancelOpen = ref(false)
const uploadFile = ref<any>(null)
const payments = ref<any>([])
const units = ref<any>([])
const sepsList = ref<any>([])
// Headers // Headers
const hreaderPrep: CH.Config = { const hreaderPrep: CH.Config = {
@@ -72,36 +87,14 @@ if (!props.canCreate) {
delete hreaderPrep.addNav delete hreaderPrep.addNav
} }
// Filters
const filter = ref<{
installation: {
msg: {
placeholder: string
}
items: {
value: string
label: string
code: string
}[]
}
schema: any
initialValues?: Partial<any>
errors?: any
}>({
installation: {
msg: {
placeholder: 'Pilih',
},
items: [],
},
schema: {},
})
// Recrod reactivities // Recrod reactivities
provide('activeServicePosition', activeServicePosition) provide('activeServicePosition', activeServicePosition)
provide('rec_id', recId) provide('rec_id', recId)
provide('rec_action', recAction) provide('rec_action', recAction)
provide('rec_item', recItem) provide('rec_item', recItem)
provide('rec_sep_id', recSepId)
provide('rec_sep_menu', recSepMenu)
provide('rec_sep_sub_menu', recSepSubMenu)
provide('table_data_loader', isLoading) provide('table_data_loader', isLoading)
watch(getActiveRole, (role?: string) => { watch(getActiveRole, (role?: string) => {
@@ -129,15 +122,50 @@ watch(
}, },
) )
onMounted(() => { watch([recSepId, recSepMenu, recSepSubMenu], (value) => {
const id = value[0]
const menu = value[1]
const subMenu = value[2]
if (!id) return
if (subMenu === 'view') {
handleViewFile(id, menu, subMenu)
}
if (subMenu === 'edit') {
handleUploadFile(id, menu)
}
})
onMounted(async () => {
getPatientList() getPatientList()
units.value = await getUnits({}, true)
payments.value = Object.keys(paymentTypes).map((item) => ({
value: item.toString(),
label: paymentTypes[item],
})) as any
sepsList.value = Object.keys(sepRefTypeCodes).map((item) => ({
value: item.toString(),
label: sepRefTypeCodes[item],
})) as any
}) })
/////// Functions /////// Functions
async function getPatientList() { async function getPatientList() {
isLoading.isTableLoading = true isLoading.isTableLoading = true
const includesParams = const includesParamsArrays = [
'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',
'Appointment_Doctor',
'Appointment_Doctor-employee',
'Appointment_Doctor-employee-person',
'Responsible_Doctor',
'Responsible_Doctor-employee',
'Responsible_Doctor-employee-person',
'EncounterDocuments',
'unit',
'vclaimReference', // vclaimReference | vclaimSep
]
const includesParams = includesParamsArrays.join(',')
data.value = [] data.value = []
try { try {
const params: any = { includes: includesParams, ...filterParams.value } const params: any = { includes: includesParams, ...filterParams.value }
@@ -159,15 +187,92 @@ async function getPatientList() {
} }
} }
function handleUploadFile(id: number, menu: string) {
uploadFile.value = null
const fileInput: HTMLInputElement | undefined | null = document
.getElementById('uploadFile')
?.querySelector("input[type='file']")
if (fileInput) {
fileInput.value = ''
fileInput.click()
}
}
async function handleUploadFileSubmit() {
const files = uploadFile.value
if (!uploadFile.value || files.length === 0) {
recSepId.value = 0
recSepMenu.value = ''
recSepSubMenu.value = ''
return
}
const result = await uploadAttachmentCustom({
file: uploadFile.value,
refId: recSepId.value,
entityTypeCode: 'encounter',
type: recSepMenu.value === 'sep' ? 'vclaim-sep' : 'vclaim-sipp',
})
if (result.success) {
toast({
title: 'Berhasil',
description: 'File berhasil diunggah',
variant: 'default',
})
await getPatientList()
} else {
toast({
title: 'Gagal',
description: 'File gagal diunggah',
variant: 'destructive',
})
}
recSepId.value = 0
recSepMenu.value = ''
recSepSubMenu.value = ''
}
function handleViewFile(id: number, menu: string, subMenu: string) {
const currentData: any = data.value.find((item: any) => Number(item.id) === Number(id))
if (!currentData) return
let fileReviewSep: any = null
let fileReviewSipp: any = null
for (const doc of currentData.encounterDocuments) {
if (doc.type_code === 'vclaim-sep') {
fileReviewSep = { id: doc.id, fileName: doc.fileName, filePath: doc.filePath, type: doc.type_code }
} else if (doc.type_code === 'vclaim-sipp') {
fileReviewSipp = { id: doc.id, fileName: doc.fileName, filePath: doc.filePath, type: doc.type_code }
}
}
if (fileReviewSep && menu === 'sep' && subMenu === 'view') {
window.open(fileReviewSep.filePath, '_blank')
}
if (fileReviewSipp && menu === 'sipp' && subMenu === 'view') {
window.open(fileReviewSipp.filePath, '_blank')
}
}
function handleFilterApply(filters: { personName: string; startDate: string; endDate: string }) { function handleFilterApply(filters: { personName: string; startDate: string; endDate: string }) {
filterParams.value = { filterParams.value = {
'person-name': filters.personName, // 'person-name': filters.personName,
'patient-identifier': filters.personName,
'start-date': filters.startDate, 'start-date': filters.startDate,
'end-date': filters.endDate, 'end-date': filters.endDate,
} }
getPatientList() getPatientList()
} }
function handleFilterReset() {
isFilterFormDialogOpen.value = false
filterParams.value = {}
getPatientList()
}
function handleFilterSearch(filters: any) {
isFilterFormDialogOpen.value = false
filterParams.value = { ...filterParams.value, ...filters }
getPatientList()
}
// Handle confirmation result // Handle confirmation result
async function handleConfirmCancel(record: any, action: string) { async function handleConfirmCancel(record: any, action: string) {
if (action === 'deactivate' && record?.id) { if (action === 'deactivate' && record?.id) {
@@ -263,23 +368,34 @@ function handleRemoveConfirmation() {
<CH.ContentHeader v-bind="hreaderPrep"> <CH.ContentHeader v-bind="hreaderPrep">
<FilterNav <FilterNav
:active-positon="activeServicePosition" :active-positon="activeServicePosition"
:enable-export="false"
@apply="handleFilterApply" @apply="handleFilterApply"
@click="() => (isFilterFormDialogOpen = true)"
@onExportPdf="() => {}" @onExportPdf="() => {}"
@onExportExcel="() => {}" @onExportExcel="() => {}"
@nExportCsv="() => {}" @nExportCsv="() => {}"
/> />
</CH.ContentHeader> </CH.ContentHeader>
<Content :data="data" /> <Content
:data="data"
:class-code="classCode"
/>
<!-- Filter --> <!-- Filter -->
<Dialog <Dialog
v-model:open="isFilterFormDialogOpen" v-model:open="isFilterFormDialogOpen"
title="Filter" title="Filter Data"
size="lg" size="lg"
prevent-outside prevent-outside
> >
<FilterForm v-bind="filter" /> <FilterForm
:payments="payments"
:units="units"
:visits="sepsList"
@search="handleFilterSearch"
@reset="handleFilterReset"
/>
</Dialog> </Dialog>
<!-- Batal --> <!-- Batal -->
@@ -340,4 +456,15 @@ function handleRemoveConfirmation() {
> >
Hak akses tidak memenuhi kriteria untuk proses ini. Hak akses tidak memenuhi kriteria untuk proses ini.
</Dialog> </Dialog>
<FileUpload
class="hidden"
field-name="uploadFile"
label="Dokumen"
placeholder="Pilih file"
:accept="['pdf', 'jpg', 'png']"
:max-size-mb="1"
v-model="uploadFile"
@file-selected="handleUploadFileSubmit"
/>
</template> </template>
+3 -2
View File
@@ -69,7 +69,7 @@ async function onFileChange(event: Event, handleChange: (value: any) => void) {
</script> </script>
<template> <template>
<DE.Cell> <DE.Cell :class="class">
<DE.Label <DE.Label
v-if="label !== ''" v-if="label !== ''"
:label-for="fieldName" :label-for="fieldName"
@@ -88,12 +88,13 @@ async function onFileChange(event: Event, handleChange: (value: any) => void) {
<FormItem> <FormItem>
<FormControl class="flex flex-col"> <FormControl class="flex flex-col">
<Input <Input
@change="onFileChange($event, handleChange)" :id="fieldName"
type="file" type="file"
:disabled="isDisabled" :disabled="isDisabled"
v-bind="{ onBlur: componentField.onBlur }" v-bind="{ onBlur: componentField.onBlur }"
:placeholder="placeholder" :placeholder="placeholder"
:class="cn('focus:border-primary focus:ring-2 focus:ring-primary focus:ring-offset-0')" :class="cn('focus:border-primary focus:ring-2 focus:ring-primary focus:ring-offset-0')"
@change="onFileChange($event, handleChange)"
/> />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
+4 -4
View File
@@ -539,7 +539,7 @@ export function useEncounterEntry(props: {
class_code: props.classCode || '', class_code: props.classCode || '',
subClass_code: props.subClassCode || '', subClass_code: props.subClassCode || '',
infra_id: formValues.infra_id ?? null, infra_id: formValues.infra_id ?? null,
unit_code: userStore?.user?.unit_code ?? null, unit_code: formValues.unitCode ?? userStore?.user?.unit_code ?? null,
refSource_name: formValues.refSource_name ?? 'RSSA', refSource_name: formValues.refSource_name ?? 'RSSA',
refTypeCode: formValues.paymentType === 'jkn' ? 'bpjs' : '', refTypeCode: formValues.paymentType === 'jkn' ? 'bpjs' : '',
vclaimReference: vclaimReference.value ?? null, vclaimReference: vclaimReference.value ?? null,
@@ -547,7 +547,7 @@ export function useEncounterEntry(props: {
registeredAt: formatDate(registeredAtValue), registeredAt: formatDate(registeredAtValue),
visitDate: formatDate(visitDateValue), visitDate: formatDate(visitDateValue),
} }
if (props.classCode !== 'inpatient') { if (props.classCode !== 'inpatient') {
delete payload.infra_id delete payload.infra_id
} }
@@ -599,7 +599,7 @@ export function useEncounterEntry(props: {
if (encounterId) { if (encounterId) {
if (sepFile.value) { if (sepFile.value) {
await uploadAttachmentCustom({ await uploadAttachmentCustom({
id: isEditMode.value && formValues.sepFileReview ? formValues.sepFileReview.id : null, // id: isEditMode.value && formValues.sepFileReview ? formValues.sepFileReview.id : null,
file: sepFile.value, file: sepFile.value,
refId: encounterId, refId: encounterId,
entityTypeCode: 'encounter', entityTypeCode: 'encounter',
@@ -608,7 +608,7 @@ export function useEncounterEntry(props: {
} }
if (sippFile.value) { if (sippFile.value) {
await uploadAttachmentCustom({ await uploadAttachmentCustom({
id: isEditMode.value && formValues.sippFileReview ? formValues.sippFileReview.id : null, // id: isEditMode.value && formValues.sippFileReview ? formValues.sippFileReview.id : null,
file: sippFile.value, file: sippFile.value,
refId: encounterId, refId: encounterId,
entityTypeCode: 'encounter', entityTypeCode: 'encounter',
+4 -1
View File
@@ -27,6 +27,9 @@ export async function uploadAttachmentCustom(payload: any) {
const { user } = useUserStore() const { user } = useUserStore()
const formData = new FormData() const formData = new FormData()
if (!payload.id && payload.name) {
formData.append('name', payload.name)
}
formData.append('content', payload.file) formData.append('content', payload.file)
formData.append('entityType_code', payload.entityTypeCode) formData.append('entityType_code', payload.entityTypeCode)
formData.append('type_code', payload.type) formData.append('type_code', payload.type)
@@ -34,5 +37,5 @@ export async function uploadAttachmentCustom(payload: any) {
formData.append('upload_employee_id', user.employee_id) formData.append('upload_employee_id', user.employee_id)
const response = payload.id ? await update(payload.id, formData) : await create(formData) const response = payload.id ? await update(payload.id, formData) : await create(formData)
return response?.body?.data return response
} }
+1 -1
View File
@@ -34,7 +34,7 @@ export default defineEventHandler(async (event) => {
let body: any let body: any
if (['POST', 'PATCH', 'PUT', 'DELETE'].includes(method)) { if (['POST', 'PATCH', 'PUT', 'DELETE'].includes(method)) {
if (headers['content-type']?.includes('multipart/form-data')) { if (headers['content-type']?.includes('multipart/form-data')) {
body = await readBody(event) body = await readRawBody(event, false) // false to get Buffer
} else { } else {
body = await readBody(event) body = await readBody(event)
forwardHeaders.set('Content-Type', 'application/json') forwardHeaders.set('Content-Type', 'application/json')