Merge branch 'dev' of https://github.com/dikstub-rssa/simrs-fe into feat/assessment-adjustment-223
This commit is contained in:
+3
-3
@@ -43,7 +43,7 @@ const emit = defineEmits<{
|
||||
}>()
|
||||
|
||||
// Validation schema
|
||||
const { handleSubmit, errors, defineField, meta } = useForm<IntegrationEncounterFormData>({
|
||||
const { handleSubmit, errors, defineField, meta } = useForm<any>({
|
||||
validationSchema: toTypedSchema(IntegrationEncounterSchema),
|
||||
})
|
||||
|
||||
@@ -141,7 +141,7 @@ function onAddSep() {
|
||||
registerDate: registerDate.value,
|
||||
cardNumber: cardNumber.value,
|
||||
paymentType: paymentType.value,
|
||||
sepType: sepType.value
|
||||
sepType: sepType.value,
|
||||
}
|
||||
emit('event', 'add-sep', formValues)
|
||||
}
|
||||
@@ -454,7 +454,7 @@ defineExpose({
|
||||
name="i-lucide-loader-2"
|
||||
class="h-4 w-4 animate-spin"
|
||||
/>
|
||||
<Icon
|
||||
<Icon
|
||||
v-else
|
||||
name="i-lucide-plus"
|
||||
class="h-4 w-4"
|
||||
@@ -187,6 +187,7 @@ function onAddSep() {
|
||||
registerDate: registerDate.value,
|
||||
cardNumber: cardNumber.value,
|
||||
paymentMethodCode: paymentMethodCode.value,
|
||||
unitCode: props.selectedDoctor?.unit?.code || '',
|
||||
sepFile: sepFile.value,
|
||||
sippFile: sippFile.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>
|
||||
@@ -1,114 +1,109 @@
|
||||
<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
|
||||
}
|
||||
// Components
|
||||
import * as DE from '~/components/pub/my-ui/doc-entry'
|
||||
|
||||
// Props
|
||||
const props = defineProps<{
|
||||
installation: {
|
||||
msg: {
|
||||
placeholder: string
|
||||
}
|
||||
items: {
|
||||
value: string
|
||||
label: string
|
||||
code: string
|
||||
}[]
|
||||
}
|
||||
schema: any
|
||||
initialValues?: Partial<InstallationFormData>
|
||||
errors?: FormErrors
|
||||
payments: any[]
|
||||
units: any[]
|
||||
visits: any[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
submit: [values: InstallationFormData, resetForm: () => void]
|
||||
cancel: [resetForm: () => void]
|
||||
(e: 'search', values: any): 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 onSubmitForm(values: any, { resetForm }: { resetForm: () => void }) {
|
||||
const formData: InstallationFormData = {
|
||||
name: values.name || '',
|
||||
code: values.code || '',
|
||||
encounterClassCode: values.encounterClassCode || '',
|
||||
}
|
||||
emit('submit', formData, resetForm)
|
||||
function handleReset() {
|
||||
paymentItem.value = null
|
||||
unitItem.value = null
|
||||
visitItem.value = null
|
||||
emit('reset')
|
||||
}
|
||||
|
||||
// Form cancel handler
|
||||
function onCancelForm({ resetForm }: { resetForm: () => void }) {
|
||||
emit('cancel', resetForm)
|
||||
function handleSearch() {
|
||||
emit('search', {
|
||||
'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>
|
||||
|
||||
<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>
|
||||
<div class="mx-auto w-full">
|
||||
<DE.Block
|
||||
labelSize="thin"
|
||||
class="!pt-0"
|
||||
:colCount="1"
|
||||
:cellFlex="false"
|
||||
>
|
||||
<DE.Cell>
|
||||
<DE.Label height="compact">Cara Bayar</DE.Label>
|
||||
<DE.Field>
|
||||
<Select
|
||||
id="paymentMethodCode"
|
||||
v-model="paymentItem"
|
||||
:items="payments || []"
|
||||
placeholder="Pilih Jenis Pembayaran"
|
||||
/>
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
|
||||
<DE.Cell>
|
||||
<DE.Label height="compact">Poliklinik</DE.Label>
|
||||
<DE.Field>
|
||||
<Select
|
||||
id="unit"
|
||||
v-model="unitItem"
|
||||
:items="units || []"
|
||||
placeholder="Pilih Poliklinik"
|
||||
/>
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
|
||||
<DE.Cell>
|
||||
<DE.Label height="compact">Kunjungan</DE.Label>
|
||||
<DE.Field>
|
||||
<Select
|
||||
id="visit"
|
||||
v-model="visitItem"
|
||||
:items="visits || []"
|
||||
placeholder="Pilih Kunjungan"
|
||||
/>
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
</DE.Block>
|
||||
|
||||
<div class="flex w-full justify-end gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
type="button"
|
||||
class="h-[40px] rounded-md border-orange-400 text-orange-400 hover:bg-green-50"
|
||||
@click="handleReset"
|
||||
>
|
||||
<Icon
|
||||
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>
|
||||
|
||||
@@ -20,11 +20,13 @@ const props = defineProps<{
|
||||
|
||||
const emit = defineEmits<{
|
||||
apply: [filters: { personName: string; startDate: string; endDate: string }]
|
||||
click: []
|
||||
}>()
|
||||
|
||||
const searchQuery = ref('')
|
||||
const isRoleRegistration = props.activePositon === 'registration'
|
||||
const isRoleMedical = props.activePositon === 'medical'
|
||||
const debouncedSearchQuery = refDebounced(searchQuery, 500)
|
||||
|
||||
const df = new DateFormatter('en-US', {
|
||||
dateStyle: 'medium',
|
||||
@@ -43,14 +45,18 @@ const oneMonthAgoCalendar = new CalendarDate(
|
||||
oneMonthAgo.getDate(),
|
||||
)
|
||||
|
||||
const value = ref({
|
||||
const dateRange = ref({
|
||||
start: oneMonthAgoCalendar,
|
||||
end: todayCalendar,
|
||||
}) as Ref<DateRange>
|
||||
|
||||
function onFilterClick() {
|
||||
const startDate = value.value.start ? value.value.start.toString() : ''
|
||||
const endDate = value.value.end ? value.value.end.toString() : startDate
|
||||
emit('click')
|
||||
}
|
||||
|
||||
function onFilterApply() {
|
||||
const startDate = dateRange.value.start ? dateRange.value.start.toString() : ''
|
||||
const endDate = dateRange.value.end ? dateRange.value.end.toString() : startDate
|
||||
|
||||
emit('apply', {
|
||||
personName: searchQuery.value,
|
||||
@@ -58,6 +64,18 @@ function onFilterClick() {
|
||||
endDate,
|
||||
})
|
||||
}
|
||||
|
||||
watch(debouncedSearchQuery, () => {
|
||||
onFilterApply()
|
||||
})
|
||||
|
||||
watch(
|
||||
dateRange,
|
||||
() => {
|
||||
onFilterApply()
|
||||
},
|
||||
{ deep: true },
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -76,18 +94,18 @@ function onFilterClick() {
|
||||
<Button
|
||||
variant="outline"
|
||||
: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" />
|
||||
<template v-if="value.start">
|
||||
<template v-if="value.end">
|
||||
{{ df.format(value.start.toDate(getLocalTimeZone())) }} -
|
||||
{{ df.format(value.end.toDate(getLocalTimeZone())) }}
|
||||
<template v-if="dateRange.start">
|
||||
<template v-if="dateRange.end">
|
||||
{{ df.format(dateRange.start.toDate(getLocalTimeZone())) }} -
|
||||
{{ df.format(dateRange.end.toDate(getLocalTimeZone())) }}
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
{{ df.format(value.start.toDate(getLocalTimeZone())) }}
|
||||
{{ df.format(dateRange.start.toDate(getLocalTimeZone())) }}
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>Pick a date</template>
|
||||
@@ -95,10 +113,10 @@ function onFilterClick() {
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="w-auto p-0">
|
||||
<RangeCalendar
|
||||
v-model="value"
|
||||
v-model="dateRange"
|
||||
initial-focus
|
||||
:number-of-months="2"
|
||||
@update:start-value="(startDate) => (value.start = startDate)"
|
||||
@update:start-value="(startDate) => (dateRange.start = startDate)"
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
@@ -112,7 +130,7 @@ function onFilterClick() {
|
||||
Filter
|
||||
</Button>
|
||||
|
||||
<DropdownMenu v-show="props.enableExport && (isRoleRegistration || isRoleMedical)">
|
||||
<DropdownMenu v-if="props.enableExport && (isRoleRegistration || isRoleMedical)">
|
||||
<DropdownMenuTrigger as-child>
|
||||
<Button
|
||||
variant="outline"
|
||||
|
||||
@@ -1,25 +1,19 @@
|
||||
import type { Config, RecComponent } from '~/components/pub/my-ui/data-table'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
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'
|
||||
|
||||
type SmallDetailDto = Encounter
|
||||
|
||||
const action = defineAsyncComponent(() => import('./dropdown-action.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 = {
|
||||
cols: [
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{ width: 160 },
|
||||
{},
|
||||
{ width: 70 },
|
||||
{ },
|
||||
{ width: 50 },
|
||||
],
|
||||
export const defaultConfig: Config = {
|
||||
cols: [{}, {}, {}, { width: 160 }, {}, { width: 70 }, {}, { width: 50 }],
|
||||
|
||||
headers: [
|
||||
[
|
||||
@@ -94,11 +88,146 @@ export const config: Config = {
|
||||
birth_date: (rec: unknown): unknown => {
|
||||
const recX = rec as Encounter
|
||||
if (recX.patient?.person?.birthDate) {
|
||||
return '' +
|
||||
'<div>' + (recX.patient.person.birthDate as string).substring(0, 10) + ' / </div>' +
|
||||
return (
|
||||
'' +
|
||||
'<div>' +
|
||||
(recX.patient.person.birthDate as string).substring(0, 10) +
|
||||
' / </div>' +
|
||||
getAge(recX.patient.person.birthDate as string).extFormat
|
||||
)
|
||||
}
|
||||
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
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
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<{
|
||||
data: any[],
|
||||
data: any[]
|
||||
classCode: string | undefined
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DataTable
|
||||
v-bind="config"
|
||||
v-bind="props.classCode === 'ambulatory' ? ambulatoryConfig : defaultConfig"
|
||||
:rows="props.data"
|
||||
/>
|
||||
</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>
|
||||
@@ -7,8 +7,12 @@ import { ActionEvents } from '~/components/pub/my-ui/data/types'
|
||||
import Dialog from '~/components/pub/my-ui/modal/dialog.vue'
|
||||
import * as CH from '~/components/pub/my-ui/content-header'
|
||||
import RecordConfirmation from '~/components/pub/my-ui/confirmation/record-confirmation.vue'
|
||||
import FileUpload from '~/components/pub/my-ui/form/file-field.vue'
|
||||
import { useSidebar } from '~/components/pub/ui/sidebar/utils'
|
||||
|
||||
// Constants
|
||||
import { paymentTypes, sepRefTypeCodes } from '~/lib/constants.vclaim'
|
||||
|
||||
// App libs
|
||||
import { getServicePosition } from '~/lib/roles' // previously getPositionAs
|
||||
|
||||
@@ -18,6 +22,10 @@ import {
|
||||
remove as removeEncounter,
|
||||
cancel as cancelEncounter,
|
||||
} from '~/services/encounter.service'
|
||||
import { getValueLabelList as getUnits } from '~/services/unit.service'
|
||||
|
||||
// Handlers
|
||||
import { uploadAttachmentCustom } from '~/handlers/supporting-document.handler'
|
||||
|
||||
// Apps
|
||||
import Content from '~/components/app/encounter/list.vue'
|
||||
@@ -53,9 +61,16 @@ const isLoading = reactive<DataTableLoader>({
|
||||
const recId = ref<number>(0)
|
||||
const recAction = ref<string>('')
|
||||
const recItem = ref<any>(null)
|
||||
const recSepId = ref<number>(0)
|
||||
const recSepMenu = ref<string>('')
|
||||
const recSepSubMenu = ref<string>('')
|
||||
const isFilterFormDialogOpen = ref(false)
|
||||
const isRecordConfirmationOpen = ref(false)
|
||||
const isRecordCancelOpen = ref(false)
|
||||
const uploadFile = ref<any>(null)
|
||||
const payments = ref<any>([])
|
||||
const units = ref<any>([])
|
||||
const sepsList = ref<any>([])
|
||||
|
||||
// Headers
|
||||
const hreaderPrep: CH.Config = {
|
||||
@@ -72,36 +87,14 @@ if (!props.canCreate) {
|
||||
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
|
||||
provide('activeServicePosition', activeServicePosition)
|
||||
provide('rec_id', recId)
|
||||
provide('rec_action', recAction)
|
||||
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)
|
||||
|
||||
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()
|
||||
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
|
||||
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'
|
||||
const includesParamsArrays = [
|
||||
'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 = []
|
||||
try {
|
||||
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 }) {
|
||||
filterParams.value = {
|
||||
'person-name': filters.personName,
|
||||
// 'person-name': filters.personName,
|
||||
'patient-identifier': filters.personName,
|
||||
'start-date': filters.startDate,
|
||||
'end-date': filters.endDate,
|
||||
}
|
||||
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
|
||||
async function handleConfirmCancel(record: any, action: string) {
|
||||
if (action === 'deactivate' && record?.id) {
|
||||
@@ -263,23 +368,34 @@ function handleRemoveConfirmation() {
|
||||
<CH.ContentHeader v-bind="hreaderPrep">
|
||||
<FilterNav
|
||||
:active-positon="activeServicePosition"
|
||||
:enable-export="false"
|
||||
@apply="handleFilterApply"
|
||||
@click="() => (isFilterFormDialogOpen = true)"
|
||||
@onExportPdf="() => {}"
|
||||
@onExportExcel="() => {}"
|
||||
@nExportCsv="() => {}"
|
||||
/>
|
||||
</CH.ContentHeader>
|
||||
|
||||
<Content :data="data" />
|
||||
<Content
|
||||
:data="data"
|
||||
:class-code="classCode"
|
||||
/>
|
||||
|
||||
<!-- Filter -->
|
||||
<Dialog
|
||||
v-model:open="isFilterFormDialogOpen"
|
||||
title="Filter"
|
||||
title="Filter Data"
|
||||
size="lg"
|
||||
prevent-outside
|
||||
>
|
||||
<FilterForm v-bind="filter" />
|
||||
<FilterForm
|
||||
:payments="payments"
|
||||
:units="units"
|
||||
:visits="sepsList"
|
||||
@search="handleFilterSearch"
|
||||
@reset="handleFilterReset"
|
||||
/>
|
||||
</Dialog>
|
||||
|
||||
<!-- Batal -->
|
||||
@@ -340,4 +456,15 @@ function handleRemoveConfirmation() {
|
||||
>
|
||||
Hak akses tidak memenuhi kriteria untuk proses ini.
|
||||
</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>
|
||||
|
||||
@@ -69,7 +69,7 @@ async function onFileChange(event: Event, handleChange: (value: any) => void) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DE.Cell>
|
||||
<DE.Cell :class="class">
|
||||
<DE.Label
|
||||
v-if="label !== ''"
|
||||
:label-for="fieldName"
|
||||
@@ -88,12 +88,13 @@ async function onFileChange(event: Event, handleChange: (value: any) => void) {
|
||||
<FormItem>
|
||||
<FormControl class="flex flex-col">
|
||||
<Input
|
||||
@change="onFileChange($event, handleChange)"
|
||||
:id="fieldName"
|
||||
type="file"
|
||||
:disabled="isDisabled"
|
||||
v-bind="{ onBlur: componentField.onBlur }"
|
||||
:placeholder="placeholder"
|
||||
:class="cn('focus:border-primary focus:ring-2 focus:ring-primary focus:ring-offset-0')"
|
||||
@change="onFileChange($event, handleChange)"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
|
||||
@@ -539,7 +539,7 @@ export function useEncounterEntry(props: {
|
||||
class_code: props.classCode || '',
|
||||
subClass_code: props.subClassCode || '',
|
||||
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',
|
||||
refTypeCode: formValues.paymentType === 'jkn' ? 'bpjs' : '',
|
||||
vclaimReference: vclaimReference.value ?? null,
|
||||
@@ -547,7 +547,7 @@ export function useEncounterEntry(props: {
|
||||
registeredAt: formatDate(registeredAtValue),
|
||||
visitDate: formatDate(visitDateValue),
|
||||
}
|
||||
|
||||
|
||||
if (props.classCode !== 'inpatient') {
|
||||
delete payload.infra_id
|
||||
}
|
||||
@@ -599,7 +599,7 @@ export function useEncounterEntry(props: {
|
||||
if (encounterId) {
|
||||
if (sepFile.value) {
|
||||
await uploadAttachmentCustom({
|
||||
id: isEditMode.value && formValues.sepFileReview ? formValues.sepFileReview.id : null,
|
||||
// id: isEditMode.value && formValues.sepFileReview ? formValues.sepFileReview.id : null,
|
||||
file: sepFile.value,
|
||||
refId: encounterId,
|
||||
entityTypeCode: 'encounter',
|
||||
@@ -608,7 +608,7 @@ export function useEncounterEntry(props: {
|
||||
}
|
||||
if (sippFile.value) {
|
||||
await uploadAttachmentCustom({
|
||||
id: isEditMode.value && formValues.sippFileReview ? formValues.sippFileReview.id : null,
|
||||
// id: isEditMode.value && formValues.sippFileReview ? formValues.sippFileReview.id : null,
|
||||
file: sippFile.value,
|
||||
refId: encounterId,
|
||||
entityTypeCode: 'encounter',
|
||||
|
||||
@@ -27,6 +27,9 @@ export async function uploadAttachmentCustom(payload: any) {
|
||||
const { user } = useUserStore()
|
||||
|
||||
const formData = new FormData()
|
||||
if (!payload.id && payload.name) {
|
||||
formData.append('name', payload.name)
|
||||
}
|
||||
formData.append('content', payload.file)
|
||||
formData.append('entityType_code', payload.entityTypeCode)
|
||||
formData.append('type_code', payload.type)
|
||||
@@ -34,5 +37,5 @@ export async function uploadAttachmentCustom(payload: any) {
|
||||
formData.append('upload_employee_id', user.employee_id)
|
||||
|
||||
const response = payload.id ? await update(payload.id, formData) : await create(formData)
|
||||
return response?.body?.data
|
||||
return response
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ export default defineEventHandler(async (event) => {
|
||||
let body: any
|
||||
if (['POST', 'PATCH', 'PUT', 'DELETE'].includes(method)) {
|
||||
if (headers['content-type']?.includes('multipart/form-data')) {
|
||||
body = await readBody(event)
|
||||
body = await readRawBody(event, false) // false to get Buffer
|
||||
} else {
|
||||
body = await readBody(event)
|
||||
forwardHeaders.set('Content-Type', 'application/json')
|
||||
|
||||
Reference in New Issue
Block a user