Merge branch 'dev' into feat/education-assessment-79
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
NUXT_MAIN_API_ORIGIN=
|
||||
NUXT_BPJS_API_ORIGIN=
|
||||
NUXT_API_VCLAIM_SWAGGER= # https://vclaim-api.multy.chat
|
||||
NUXT_SYNC_API_ORIGIN=
|
||||
NUXT_API_ORIGIN=
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import type { Config, RecComponent } from '~/components/pub/my-ui/data-table'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
type SmallDetailDto = any
|
||||
|
||||
const action = defineAsyncComponent(() => import('~/components/pub/my-ui/data/dropdown-action-dud.vue'))
|
||||
|
||||
export const config: Config = {
|
||||
cols: [{}, {}, {}, { width: 50 }],
|
||||
|
||||
headers: [[
|
||||
{ label: 'Tgl. Order' },
|
||||
{ label: 'No. Order' },
|
||||
{ label: 'Jadwal Pemeriksaan' },
|
||||
{ label: 'Lokalisasi' },
|
||||
{ label: 'Stadium' },
|
||||
{ label: 'Status' },
|
||||
{ label: 'Resume' },
|
||||
{ label: '' }]],
|
||||
|
||||
keys: [
|
||||
'date',
|
||||
'number',
|
||||
'examinationDate',
|
||||
'localization',
|
||||
'stadium',
|
||||
'resume',
|
||||
'',
|
||||
],
|
||||
|
||||
delKeyNames: [
|
||||
{ key: 'date', label: 'Tanggal' },
|
||||
{ key: 'number', label: 'Nomor' },
|
||||
],
|
||||
|
||||
parses: {
|
||||
parent: (rec: unknown): unknown => {
|
||||
const recX = rec as SmallDetailDto
|
||||
return recX.parent?.name || '-'
|
||||
},
|
||||
},
|
||||
|
||||
components: {
|
||||
action(rec, idx) {
|
||||
const res: RecComponent = {
|
||||
idx,
|
||||
rec: rec as object,
|
||||
component: action,
|
||||
props: {
|
||||
size: 'sm',
|
||||
},
|
||||
}
|
||||
return res
|
||||
},
|
||||
},
|
||||
|
||||
htmls: {},
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<script setup lang="ts">
|
||||
// Components
|
||||
import DataTable from '~/components/pub/my-ui/data-table/data-table.vue'
|
||||
import PaginationView from '~/components/pub/my-ui/pagination/pagination-view.vue'
|
||||
|
||||
// Types
|
||||
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
|
||||
|
||||
// Configs
|
||||
import { config } from './list.cfg'
|
||||
|
||||
interface Props {
|
||||
data: any[]
|
||||
paginationMeta: PaginationMeta
|
||||
}
|
||||
|
||||
defineProps<Props>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
pageChange: [page: number]
|
||||
}>()
|
||||
|
||||
function handlePageChange(page: number) {
|
||||
emit('pageChange', page)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<DataTable
|
||||
v-bind="config"
|
||||
:rows="data"
|
||||
:skeleton-size="paginationMeta?.pageSize"
|
||||
/>
|
||||
<PaginationView :pagination-meta="paginationMeta" @page-change="handlePageChange" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,699 @@
|
||||
<script setup lang="ts">
|
||||
// Components
|
||||
import * as DE from '~/components/pub/my-ui/doc-entry'
|
||||
import { Button } from '~/components/pub/ui/button'
|
||||
import { Input } from '~/components/pub/ui/input'
|
||||
import * as CB from '~/components/pub/my-ui/combobox'
|
||||
|
||||
import DatepickerSingle from '~/components/pub/my-ui/datepicker/datepicker-single.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'
|
||||
|
||||
// Helpers
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
import { useForm } from 'vee-validate'
|
||||
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<{
|
||||
mode?: string
|
||||
isLoading?: boolean
|
||||
isReadonly?: boolean
|
||||
isSepValid?: boolean
|
||||
isMemberValid?: boolean
|
||||
isCheckingSep?: boolean
|
||||
doctorItems?: CB.Item[]
|
||||
selectedDoctor: Doctor
|
||||
// subSpecialist?: any[]
|
||||
// specialists?: TreeItem[]
|
||||
payments?: any[]
|
||||
participantGroups?: any[]
|
||||
seps: any[]
|
||||
patient?: PatientEntity | null | undefined
|
||||
objects?: any
|
||||
}>()
|
||||
|
||||
// Model
|
||||
const model = defineModel<Encounter>()
|
||||
model.value = genEncounter()
|
||||
|
||||
// Common preparation
|
||||
const defaultCBItems = [{ label: 'Pilih', value: '' }]
|
||||
const paymentMethodItems = ref<any>({})
|
||||
|
||||
// Emit preparation
|
||||
const emit = defineEmits<{
|
||||
(e: 'onSelectDoctor', code: string): void
|
||||
(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 [doctorCode, doctorCodeAttrs] = defineField('doctor_code')
|
||||
const [unitCode, unitCodeAttrs] = defineField('unit_code')
|
||||
const [registerDate, registerDateAttrs] = defineField('registerDate')
|
||||
const [paymentMethodCode, paymentMethodCodeAttrs] = defineField('paymentMethod_code')
|
||||
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 [sepFile, sepFileAttrs] = defineField('sepFile')
|
||||
const [sippFile, sippFileAttrs] = defineField('sippFile')
|
||||
const patientId = ref('')
|
||||
const sepReference = ref('')
|
||||
const sepControlDate = ref('')
|
||||
const sepTrafficStatus = ref('')
|
||||
const diagnosis = ref('')
|
||||
const noteReference = ref('Hanya diperlukan jika pembayaran jenis JKN')
|
||||
const noteFile = ref('Gunakan file [.pdf, .jpg, .png] dengan ukuran maksimal 1MB')
|
||||
|
||||
const isLoading = props.isLoading !== undefined ? props.isLoading : false
|
||||
const isReadonly = props.isReadonly !== undefined ? props.isReadonly : false
|
||||
const mode = props.mode !== undefined ? props.mode : 'add'
|
||||
// SEP validation state from props
|
||||
const isSepValid = computed(() => props.isSepValid || false)
|
||||
const isCheckingSep = computed(() => props.isCheckingSep || false)
|
||||
const isInsurancePayment = computed(() => ['insurance', 'jkn'].includes(paymentMethodCode.value))
|
||||
const isDateLoading = ref(false)
|
||||
const debouncedSepNumber = refDebounced(sepNumber, 500)
|
||||
const debouncedCardNumber = refDebounced(cardNumber, 500)
|
||||
const sepFileReview = ref<any>(null)
|
||||
const sippFileReview = ref<any>(null)
|
||||
const unitFullName = ref('') // Unit, specialist, subspecialist
|
||||
const formRef = ref<HTMLFormElement | null>(null) // Expose submit method for parent component
|
||||
|
||||
if (mode === 'add') {
|
||||
// Set default sepDate to current date in YYYY-MM-DD format
|
||||
const today = new Date()
|
||||
const year = today.getFullYear()
|
||||
const month = String(today.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(today.getDate()).padStart(2, '0')
|
||||
registerDate.value = `${year}-${month}-${day}`
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.selectedDoctor,
|
||||
(doctor) => {
|
||||
unitFullName.value = doctor.subspecialist?.name ?? doctor.specialist?.name ?? doctor.unit?.name ?? 'tidak diketahui'
|
||||
model.value!.unit_code = doctor.unit_code || ''
|
||||
model.value!.specialist_code = doctor.specialist_code || ''
|
||||
model.value!.subspecialist_code = doctor.subspecialist_code || ''
|
||||
},
|
||||
)
|
||||
|
||||
// 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 || ''
|
||||
doctorCode.value = objects?.doctorCode || ''
|
||||
paymentMethodCode.value = objects?.paymentMethodCode || ''
|
||||
patientCategory.value = objects?.patientCategory || ''
|
||||
cardNumber.value = objects?.cardNumber || ''
|
||||
sepType.value = objects?.sepType || ''
|
||||
sepNumber.value = objects?.sepNumber || ''
|
||||
sepFileReview.value = objects?.sepFileReview || ''
|
||||
sippFileReview.value = objects?.sippFileReview || ''
|
||||
isDateLoading.value = true
|
||||
setTimeout(() => {
|
||||
registerDate.value = objects?.registerDate || ''
|
||||
isDateLoading.value = false
|
||||
}, 100)
|
||||
}
|
||||
},
|
||||
{ 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 },
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.isSepValid,
|
||||
(value) => {
|
||||
if (!value) return
|
||||
const objects = props.objects
|
||||
if (objects && Object.keys(objects).length > 0) {
|
||||
sepReference.value = objects?.sepReference || ''
|
||||
sepControlDate.value = objects?.sepControlDate || ''
|
||||
sepTrafficStatus.value = objects?.sepTrafficStatus || ''
|
||||
diagnosis.value = objects?.diagnosis || ''
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
watch(debouncedSepNumber, (newValue) => {
|
||||
emit('event', 'sep-number-changed', newValue)
|
||||
})
|
||||
|
||||
watch(debouncedCardNumber, (newValue) => {
|
||||
emit('event', 'member-changed', newValue)
|
||||
})
|
||||
|
||||
function onAddSep() {
|
||||
const formValues = {
|
||||
patientId: patientId.value || '',
|
||||
doctorCode: doctorCode.value,
|
||||
registerDate: registerDate.value,
|
||||
cardNumber: cardNumber.value,
|
||||
paymentMethodCode: paymentMethodCode.value,
|
||||
sepFile: sepFile.value,
|
||||
sippFile: sippFile.value,
|
||||
sepType: sepType.value,
|
||||
}
|
||||
emit('event', 'add-sep', formValues)
|
||||
}
|
||||
|
||||
function onSearchSep() {
|
||||
emit('event', 'search-sep', { cardNumber: cardNumber.value })
|
||||
}
|
||||
|
||||
// Submit handler
|
||||
const onSubmit = handleSubmit((values) => {
|
||||
let payload: any = values
|
||||
if (props.mode === 'edit') {
|
||||
payload = {
|
||||
...payload,
|
||||
sepFileReview: sepFileReview.value,
|
||||
sippFileReview: sippFileReview.value,
|
||||
}
|
||||
}
|
||||
emit('event', 'save', payload)
|
||||
})
|
||||
|
||||
function openFile(path: string) {
|
||||
window.open(path, '_blank')
|
||||
}
|
||||
|
||||
function submitForm() {
|
||||
// Trigger form submit using native form submit
|
||||
// This will trigger validation and onSubmit handler
|
||||
if (formRef.value) {
|
||||
formRef.value.requestSubmit()
|
||||
} else {
|
||||
// Fallback: directly call onSubmit handler
|
||||
// Create a mock event object
|
||||
const mockEvent = {
|
||||
preventDefault: () => {},
|
||||
target: formRef.value || {},
|
||||
} as SubmitEvent
|
||||
|
||||
// Call onSubmit directly
|
||||
onSubmit(mockEvent)
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
submitForm,
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
const isPaymentMethodVclaim = true
|
||||
paymentMethodItems.value = isPaymentMethodVclaim ? props.payments : CB.recStrToItem(paymentMethodCodes)
|
||||
})
|
||||
</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>
|
||||
|
||||
<DE.Block
|
||||
labelSize="thin"
|
||||
class="!pt-0"
|
||||
:colCount="3"
|
||||
:cellFlex="false"
|
||||
>
|
||||
<DE.Cell>
|
||||
<DE.Label height="compact">Nama Pasien</DE.Label>
|
||||
<DE.Field :errMessage="errors.patientName">
|
||||
<Input
|
||||
id="patientName"
|
||||
v-model="patientName"
|
||||
v-bind="patientNameAttrs"
|
||||
:disabled="true"
|
||||
/>
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
|
||||
<DE.Cell>
|
||||
<DE.Label height="compact">NIK</DE.Label>
|
||||
<DE.Field :errMessage="errors.nationalIdentity">
|
||||
<Input
|
||||
id="nationalIdentity"
|
||||
v-model="nationalIdentity"
|
||||
v-bind="nationalIdentityAttrs"
|
||||
:disabled="true"
|
||||
/>
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
|
||||
<DE.Cell>
|
||||
<DE.Label height="compact">No. RM</DE.Label>
|
||||
<DE.Field :errMessage="errors.medicalRecordNumber">
|
||||
<Input
|
||||
id="medicalRecordNumber"
|
||||
v-model="medicalRecordNumber"
|
||||
v-bind="medicalRecordNumberAttrs"
|
||||
:disabled="true"
|
||||
/>
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
</DE.Block>
|
||||
|
||||
<hr />
|
||||
|
||||
<!-- Data Kunjungan -->
|
||||
<h3 class="text-lg font-semibold">Data Kunjungan</h3>
|
||||
|
||||
<DE.Block
|
||||
labelSize="thin"
|
||||
class="!pt-0"
|
||||
:colCount="3"
|
||||
:cellFlex="false"
|
||||
>
|
||||
<DE.Cell>
|
||||
<DE.Label height="compact">
|
||||
Dokter
|
||||
<span class="text-red-500">*</span>
|
||||
</DE.Label>
|
||||
<DE.Field :errMessage="errors.doctor_code">
|
||||
<CB.Combobox
|
||||
id="doctorCode"
|
||||
v-model="doctorCode"
|
||||
v-bind="doctorCodeAttrs"
|
||||
:items="[...defaultCBItems, ...doctorItems]"
|
||||
:is-disabled="isLoading || isReadonly"
|
||||
placeholder="Pilih Dokter"
|
||||
search-placeholder="Cari Dokter"
|
||||
empty-message="Dokter tidak ditemukan"
|
||||
@update:model-value="(value: any) => emit('onSelectDoctor', value)"
|
||||
/>
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
|
||||
<DE.Cell>
|
||||
<DE.Label height="compact">
|
||||
Spesialis / Subspesialis
|
||||
<span class="text-red-500">*</span>
|
||||
</DE.Label>
|
||||
<DE.Field :errMessage="errors.unit_code">
|
||||
<Input
|
||||
:value="unitFullName"
|
||||
:disabled="true"
|
||||
/>
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
|
||||
<DE.Cell>
|
||||
<DE.Label height="compact">
|
||||
Tanggal Daftar
|
||||
<span class="text-red-500">*</span>
|
||||
</DE.Label>
|
||||
<DE.Field :errMessage="errors.registerDate">
|
||||
<DatepickerSingle
|
||||
v-if="!isDateLoading"
|
||||
id="registerDate"
|
||||
v-model="registerDate"
|
||||
v-bind="registerDateAttrs"
|
||||
placeholder="Pilih tanggal"
|
||||
/>
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
</DE.Block>
|
||||
|
||||
<DE.Block
|
||||
labelSize="thin"
|
||||
class="!pt-0"
|
||||
:colCount="3"
|
||||
:cellFlex="false"
|
||||
>
|
||||
<DE.Cell>
|
||||
<DE.Label height="compact">
|
||||
Jenis Pembayaran
|
||||
<span class="text-red-500">*</span>
|
||||
</DE.Label>
|
||||
<DE.Field :errMessage="errors.paymentMethod_code">
|
||||
<CB.Combobox
|
||||
id="paymentMethodCode"
|
||||
v-model="paymentMethodCode"
|
||||
v-bind="paymentMethodCodeAttrs"
|
||||
:items="paymentMethodItems"
|
||||
:disabled="isLoading || isReadonly"
|
||||
placeholder="Pilih Jenis Pembayaran"
|
||||
/>
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
</DE.Block>
|
||||
|
||||
<!-- BPJS Fields (conditional) -->
|
||||
<template v-if="isInsurancePayment">
|
||||
<DE.Block
|
||||
labelSize="thin"
|
||||
class="!pt-0"
|
||||
:colCount="3"
|
||||
:cellFlex="false"
|
||||
>
|
||||
<DE.Cell>
|
||||
<DE.Label height="compact">
|
||||
Kelompok Peserta
|
||||
<span class="text-red-500">*</span>
|
||||
</DE.Label>
|
||||
<DE.Field :errMessage="errors.patientCategory">
|
||||
<Select
|
||||
id="patientCategory"
|
||||
v-model="patientCategory"
|
||||
v-bind="patientCategoryAttrs"
|
||||
:items="participantGroups || []"
|
||||
:disabled="isLoading || isReadonly"
|
||||
placeholder="Pilih Kelompok Peserta"
|
||||
/>
|
||||
</DE.Field>
|
||||
<span class="text-sm text-gray-500">
|
||||
{{ noteReference }}
|
||||
</span>
|
||||
</DE.Cell>
|
||||
|
||||
<DE.Cell>
|
||||
<DE.Label height="compact">
|
||||
No. Kartu BPJS
|
||||
<span class="text-red-500">*</span>
|
||||
</DE.Label>
|
||||
<DE.Field :errMessage="errors.cardNumber">
|
||||
<Input
|
||||
id="cardNumber"
|
||||
v-model="cardNumber"
|
||||
v-bind="cardNumberAttrs"
|
||||
:disabled="isLoading || isReadonly"
|
||||
placeholder="Masukkan nomor kartu BPJS"
|
||||
/>
|
||||
</DE.Field>
|
||||
<div
|
||||
v-if="isMemberValid"
|
||||
class="mt-1 flex items-center gap-2"
|
||||
>
|
||||
<Icon
|
||||
name="i-lucide-badge-check"
|
||||
class="h-4 w-4 bg-green-500 text-white"
|
||||
/>
|
||||
<span class="text-sm text-green-500">Aktif</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="!isMemberValid"
|
||||
class="mt-1 flex items-center gap-2"
|
||||
>
|
||||
<Icon
|
||||
name="i-lucide-x"
|
||||
class="h-4 w-4 bg-red-500 text-white"
|
||||
/>
|
||||
<span class="text-sm text-red-500">Tidak aktif</span>
|
||||
</div>
|
||||
</DE.Cell>
|
||||
|
||||
<DE.Cell>
|
||||
<DE.Label height="compact">
|
||||
Jenis SEP
|
||||
<span class="text-red-500">*</span>
|
||||
</DE.Label>
|
||||
<DE.Field :errMessage="errors.sepType">
|
||||
<Select
|
||||
id="sepType"
|
||||
v-model="sepType"
|
||||
v-bind="sepTypeAttrs"
|
||||
:items="seps"
|
||||
:disabled="isLoading || isReadonly"
|
||||
placeholder="Pilih Jenis SEP"
|
||||
/>
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
</DE.Block>
|
||||
|
||||
<DE.Block
|
||||
labelSize="thin"
|
||||
class="!pt-0"
|
||||
:colCount="3"
|
||||
:cellFlex="false"
|
||||
>
|
||||
<DE.Cell>
|
||||
<DE.Label height="compact">
|
||||
No. SEP
|
||||
<span class="text-red-500">*</span>
|
||||
</DE.Label>
|
||||
<DE.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 || isSepValid"
|
||||
/>
|
||||
<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-if="isMemberValid"
|
||||
variant="outline"
|
||||
type="button"
|
||||
class="bg-primary"
|
||||
size="sm"
|
||||
@click="onSearchSep"
|
||||
>
|
||||
<Icon
|
||||
name="i-lucide-search"
|
||||
class="h-4 w-4"
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
</DE.Field>
|
||||
<div
|
||||
v-if="isSepValid"
|
||||
class="mt-1 flex items-center gap-2"
|
||||
>
|
||||
<Icon
|
||||
name="i-lucide-badge-check"
|
||||
class="h-4 w-4 bg-green-500 text-white"
|
||||
/>
|
||||
<span class="text-sm text-green-500">Aktif</span>
|
||||
</div>
|
||||
<span class="text-sm text-gray-500">
|
||||
{{ noteReference }}
|
||||
</span>
|
||||
</DE.Cell>
|
||||
|
||||
<DE.Cell>
|
||||
<FileUpload
|
||||
field-name="sepFile"
|
||||
label="Dokumen SEP"
|
||||
placeholder="Pilih file"
|
||||
:accept="['pdf', 'jpg', 'png']"
|
||||
:max-size-mb="1"
|
||||
v-model="sepFile"
|
||||
v-bind="sepFileAttrs"
|
||||
@file-selected="() => {}"
|
||||
/>
|
||||
<span class="mt-1 text-sm text-gray-500">
|
||||
{{ noteFile }}
|
||||
</span>
|
||||
<p v-if="sepFileReview">
|
||||
<a
|
||||
class="mt-1 text-sm capitalize text-blue-500"
|
||||
href="#"
|
||||
@click="openFile(sepFileReview.filePath)"
|
||||
>
|
||||
{{ sepFileReview?.fileName }}
|
||||
</a>
|
||||
</p>
|
||||
</DE.Cell>
|
||||
|
||||
<DE.Cell>
|
||||
<FileUpload
|
||||
field-name="sippFile"
|
||||
label="Dokumen SIPP"
|
||||
placeholder="Pilih file"
|
||||
:accept="['pdf', 'jpg', 'png']"
|
||||
:max-size-mb="1"
|
||||
v-model="sippFile"
|
||||
v-bind="sippFileAttrs"
|
||||
@file-selected="() => {}"
|
||||
/>
|
||||
<span class="mt-1 text-sm text-gray-500">
|
||||
{{ noteFile }}
|
||||
</span>
|
||||
<p v-if="sippFileReview">
|
||||
<a
|
||||
class="mt-1 text-sm capitalize text-blue-500"
|
||||
href="#"
|
||||
@click="openFile(sippFileReview.filePath)"
|
||||
>
|
||||
{{ sippFileReview?.fileName }}
|
||||
</a>
|
||||
</p>
|
||||
</DE.Cell>
|
||||
</DE.Block>
|
||||
</template>
|
||||
|
||||
<template v-if="isSepValid">
|
||||
<hr />
|
||||
<!-- Data SEP -->
|
||||
<h3 class="text-lg font-semibold">Data SEP</h3>
|
||||
|
||||
<DE.Block
|
||||
labelSize="thin"
|
||||
class="!pt-0"
|
||||
:colCount="3"
|
||||
:cellFlex="false"
|
||||
>
|
||||
<DE.Cell>
|
||||
<Label height="compact">Dengan Rujukan / Surat Kontrol</Label>
|
||||
<DE.Field>
|
||||
<Input
|
||||
id="sepReference"
|
||||
v-model="sepReference"
|
||||
:disabled="true"
|
||||
/>
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
|
||||
<DE.Cell>
|
||||
<Label height="compact">No. Rujukan / Surat Kontrol</Label>
|
||||
<DE.Field>
|
||||
<Input
|
||||
id="sepReference"
|
||||
v-model="sepNumber"
|
||||
:disabled="true"
|
||||
/>
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
|
||||
<DE.Cell>
|
||||
<Label height="compact">
|
||||
Tanggal Rujukan / Surat Kontrol
|
||||
<span class="ml-1 text-red-500">*</span>
|
||||
</Label>
|
||||
<DE.Field>
|
||||
<DatepickerSingle
|
||||
id="sepControlDate"
|
||||
v-model="sepControlDate"
|
||||
:disabled="true"
|
||||
placeholder="Pilih tanggal sep"
|
||||
/>
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
</DE.Block>
|
||||
|
||||
<DE.Block
|
||||
labelSize="thin"
|
||||
class="!pt-0"
|
||||
:colCount="3"
|
||||
:cellFlex="false"
|
||||
>
|
||||
<DE.Cell :col-span="2">
|
||||
<Label height="compact">Diagnosis</Label>
|
||||
<DE.Field>
|
||||
<Input
|
||||
id="diagnosis"
|
||||
v-model="diagnosis"
|
||||
:disabled="true"
|
||||
/>
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
|
||||
<DE.Cell>
|
||||
<Label height="compact">Status Kecelakaan</Label>
|
||||
<DE.Field>
|
||||
<Input
|
||||
id="sepTrafficStatus"
|
||||
v-model="sepTrafficStatus"
|
||||
:disabled="true"
|
||||
/>
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
</DE.Block>
|
||||
</template>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
@@ -23,7 +23,6 @@ import { paymentMethodCodes } from '~/const/key-val/common'
|
||||
|
||||
// App things
|
||||
import { genEncounter, type Encounter } from '~/models/encounter'
|
||||
import { se } from 'date-fns/locale'
|
||||
|
||||
// Props
|
||||
const props = defineProps<{
|
||||
@@ -50,7 +49,6 @@ model.value = genEncounter()
|
||||
|
||||
// Common preparation
|
||||
const defaultCBItems = [{ label: 'Pilih', value: '' }]
|
||||
const paymentMethodItems = CB.recStrToItem(paymentMethodCodes)
|
||||
|
||||
// Emit preparation
|
||||
const emit = defineEmits<{
|
||||
@@ -85,12 +83,10 @@ const sepTrafficStatus = ref('')
|
||||
const diagnosis = ref('')
|
||||
const noteReference = ref('Hanya diperlukan jika pembayaran jenis JKN')
|
||||
const noteFile = ref('Gunakan file [.pdf, .jpg, .png] dengan ukuran maksimal 1MB')
|
||||
|
||||
const mode = props.mode !== undefined ? props.mode : 'add'
|
||||
const isLoading = props.isLoading !== undefined ? props.isLoading : false
|
||||
const isReadonly = props.isReadonly !== undefined ? props.isReadonly : false
|
||||
const mode = props.mode !== undefined ? props.mode : 'add'
|
||||
// SEP validation state from props
|
||||
const isSepValid = computed(() => props.isSepValid || false)
|
||||
const isSepValid = computed(() => props.isSepValid || false) // SEP validation state from props
|
||||
const isCheckingSep = computed(() => props.isCheckingSep || false)
|
||||
const isInsurancePayment = computed(() => ['insurance', 'jkn'].includes(paymentMethodCode.value))
|
||||
const isDateLoading = ref(false)
|
||||
@@ -100,6 +96,7 @@ const sepFileReview = ref<any>(null)
|
||||
const sippFileReview = ref<any>(null)
|
||||
const unitFullName = ref('') // Unit, specialist, subspecialist
|
||||
const formRef = ref<HTMLFormElement | null>(null) // Expose submit method for parent component
|
||||
const paymentMethodItems = CB.recStrToItem(paymentMethodCodes)
|
||||
|
||||
if (mode === 'add') {
|
||||
// Set default sepDate to current date in YYYY-MM-DD format
|
||||
@@ -129,13 +126,15 @@ watch(
|
||||
nationalIdentity.value = objects?.nationalIdentity || ''
|
||||
medicalRecordNumber.value = objects?.medicalRecordNumber || ''
|
||||
doctorCode.value = objects?.doctorCode || ''
|
||||
paymentMethodCode.value = objects?.paymentMethodCode || ''
|
||||
patientCategory.value = objects?.patientCategory || ''
|
||||
cardNumber.value = objects?.cardNumber || ''
|
||||
sepType.value = objects?.sepType || ''
|
||||
sepNumber.value = objects?.sepNumber || ''
|
||||
sepFileReview.value = objects?.sepFileReview || ''
|
||||
sippFileReview.value = objects?.sippFileReview || ''
|
||||
if (objects.paymentType) {
|
||||
paymentMethodCode.value = objects.paymentType || ''
|
||||
}
|
||||
isDateLoading.value = true
|
||||
setTimeout(() => {
|
||||
registerDate.value = objects?.registerDate || ''
|
||||
@@ -348,7 +347,7 @@ defineExpose({
|
||||
placeholder="Pilih Dokter"
|
||||
search-placeholder="Cari Dokter"
|
||||
empty-message="Dokter tidak ditemukan"
|
||||
@update:model-value="(value) => emit('onSelectDoctor', value)"
|
||||
@update:model-value="(value: any) => emit('onSelectDoctor', value)"
|
||||
/>
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
@@ -395,12 +394,12 @@ defineExpose({
|
||||
<span class="text-red-500">*</span>
|
||||
</DE.Label>
|
||||
<DE.Field :errMessage="errors.paymentMethod_code">
|
||||
<CB.Combobox
|
||||
<Select
|
||||
id="paymentMethodCode"
|
||||
v-model="paymentMethodCode"
|
||||
v-bind="paymentMethodCodeAttrs"
|
||||
:items="paymentMethodItems"
|
||||
:disabled="isLoading || isReadonly"
|
||||
:items="payments || []"
|
||||
:disabled="isLoading || isReadonly || mode === 'edit'"
|
||||
placeholder="Pilih Jenis Pembayaran"
|
||||
/>
|
||||
</DE.Field>
|
||||
@@ -576,7 +575,7 @@ defineExpose({
|
||||
</span>
|
||||
<p v-if="sepFileReview">
|
||||
<a
|
||||
class="mt-1 text-sm text-blue-500 capitalize"
|
||||
class="mt-1 text-sm capitalize text-blue-500"
|
||||
href="#"
|
||||
@click="openFile(sepFileReview.filePath)"
|
||||
>
|
||||
@@ -601,7 +600,7 @@ defineExpose({
|
||||
</span>
|
||||
<p v-if="sippFileReview">
|
||||
<a
|
||||
class="mt-1 text-sm text-blue-500 capitalize"
|
||||
class="mt-1 text-sm capitalize text-blue-500"
|
||||
href="#"
|
||||
@click="openFile(sippFileReview.filePath)"
|
||||
>
|
||||
|
||||
@@ -1,50 +1,140 @@
|
||||
<script setup lang="ts">
|
||||
import Block from '~/components/pub/my-ui/form/block.vue'
|
||||
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'
|
||||
// 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/Button.vue'
|
||||
|
||||
const props = defineProps<{ modelValue: any }>()
|
||||
const emit = defineEmits(['update:modelValue', 'event'])
|
||||
// Types
|
||||
import type { ItemPriceFormData } from '~/schemas/item-price.schema'
|
||||
|
||||
const data = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (val) => emit('update:modelValue', val),
|
||||
// Helpers
|
||||
import type z from 'zod'
|
||||
import { useForm } from 'vee-validate'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
|
||||
interface Props {
|
||||
schema: z.ZodSchema<any>
|
||||
items: any[]
|
||||
values: any
|
||||
isLoading?: boolean
|
||||
isReadonly?: boolean
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const isLoading = props.isLoading !== undefined ? props.isLoading : false
|
||||
const isReadonly = props.isReadonly !== undefined ? props.isReadonly : false
|
||||
const emit = defineEmits<{
|
||||
submit: [values: ItemPriceFormData, resetForm: () => void]
|
||||
cancel: [resetForm: () => void]
|
||||
}>()
|
||||
|
||||
const { defineField, errors, meta } = useForm({
|
||||
validationSchema: toTypedSchema(props.schema),
|
||||
initialValues: {
|
||||
item_code: '',
|
||||
price: 0,
|
||||
insuranceCompany_code: '',
|
||||
} as Partial<ItemPriceFormData>,
|
||||
})
|
||||
|
||||
const items = [
|
||||
{ value: '1', label: 'item 1' },
|
||||
{ value: '2', label: 'item 2' },
|
||||
{ value: '3', label: 'item 3' },
|
||||
{ value: '4', label: 'item 4' },
|
||||
]
|
||||
const [item_code, item_codeAttrs] = defineField('item_code')
|
||||
const [price, priceAttrs] = defineField('price')
|
||||
const [insuranceCompany_code, insuranceCompany_codeAttrs] = defineField('insuranceCompany_code')
|
||||
|
||||
if (props.values) {
|
||||
if (props.values.item_code !== undefined) item_code.value = props.values.item_code
|
||||
if (props.values.price !== undefined) price.value = props.values.price
|
||||
if (props.values.insuranceCompany_code !== undefined) insuranceCompany_code.value = props.values.insuranceCompany_code
|
||||
}
|
||||
|
||||
const resetForm = () => {
|
||||
item_code.value = ''
|
||||
price.value = 0
|
||||
insuranceCompany_code.value = ''
|
||||
}
|
||||
|
||||
function onSubmitForm() {
|
||||
const formData: ItemPriceFormData = {
|
||||
item_code: item_code.value || '',
|
||||
price: Number(price.value) || 0,
|
||||
insuranceCompany_code: insuranceCompany_code.value || '',
|
||||
}
|
||||
emit('submit', formData, resetForm)
|
||||
}
|
||||
|
||||
function onCancelForm() {
|
||||
emit('cancel', resetForm)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form id="entry-form">
|
||||
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg xl:text-xl">
|
||||
<div class="flex flex-col justify-between">
|
||||
<Block>
|
||||
<FieldGroup>
|
||||
<Label>Items</Label>
|
||||
<Field>
|
||||
<Select :items="items" />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
<FieldGroup>
|
||||
<Label>Perusahaan Insuransi</Label>
|
||||
<Field>
|
||||
<Select :items="items" />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
<FieldGroup>
|
||||
<Label>Harga</Label>
|
||||
<Field>
|
||||
<Input v-model="data.price" />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</Block>
|
||||
</div>
|
||||
<form
|
||||
id="form-item-price"
|
||||
@submit.prevent
|
||||
>
|
||||
<Block
|
||||
labelSize="thin"
|
||||
class="!mb-2.5 !pt-0 xl:!mb-3"
|
||||
:colCount="1"
|
||||
>
|
||||
<Cell>
|
||||
<Label height="compact">Item</Label>
|
||||
<Field :errMessage="errors.item_code">
|
||||
<Select
|
||||
id="item_code"
|
||||
v-model="item_code"
|
||||
icon-name="i-lucide-chevron-down"
|
||||
placeholder="Pilih Item"
|
||||
v-bind="item_codeAttrs"
|
||||
:items="items"
|
||||
:disabled="isLoading || isReadonly"
|
||||
/>
|
||||
</Field>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<Label height="compact">Harga</Label>
|
||||
<Field :errMessage="errors.price">
|
||||
<Input
|
||||
id="price"
|
||||
type="number"
|
||||
v-model="price"
|
||||
v-bind="priceAttrs"
|
||||
:disabled="isLoading || isReadonly"
|
||||
/>
|
||||
</Field>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<Label height="compact">Perusahaan Asuransi</Label>
|
||||
<Field :errMessage="errors.insuranceCompany_code">
|
||||
<Input
|
||||
id="insuranceCompany_code"
|
||||
v-model="insuranceCompany_code"
|
||||
v-bind="insuranceCompany_codeAttrs"
|
||||
:disabled="isLoading || isReadonly"
|
||||
/>
|
||||
</Field>
|
||||
</Cell>
|
||||
</Block>
|
||||
<div class="my-2 flex justify-end gap-2 py-2">
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
class="w-[120px]"
|
||||
@click="onCancelForm"
|
||||
>
|
||||
Kembali
|
||||
</Button>
|
||||
<Button
|
||||
v-if="!isReadonly"
|
||||
type="button"
|
||||
class="w-[120px]"
|
||||
:disabled="isLoading || !meta.valid"
|
||||
@click="onSubmitForm"
|
||||
>
|
||||
Simpan
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
@@ -3,27 +3,23 @@ import { defineAsyncComponent } from 'vue'
|
||||
|
||||
const action = defineAsyncComponent(() => import('~/components/pub/my-ui/data/dropdown-action-dud.vue'))
|
||||
|
||||
const _doctorStatus = {
|
||||
0: 'Tidak Aktif',
|
||||
1: 'Aktif',
|
||||
}
|
||||
|
||||
export const config: Config = {
|
||||
cols: [{}, {}, { width: 50 }],
|
||||
cols: [{}, {}, {}, { width: 50 }],
|
||||
|
||||
headers: [
|
||||
[
|
||||
{ label: 'Kode' },
|
||||
{ label: 'Nama' },
|
||||
{ label: 'Item' },
|
||||
{ label: 'Harga' },
|
||||
{ label: 'Perusahaan Asuransi' },
|
||||
{ label: 'Aksi' },
|
||||
],
|
||||
],
|
||||
|
||||
keys: ['code', 'name', 'action'],
|
||||
keys: ['item_code', 'price', 'insuranceCompany_code', 'action'],
|
||||
|
||||
delKeyNames: [
|
||||
{ key: 'code', label: 'Kode' },
|
||||
{ key: 'name', label: 'Nama' },
|
||||
{ key: 'item_code', label: 'Item' },
|
||||
{ key: 'insuranceCompany_code', label: 'Perusahaan Asuransi' },
|
||||
],
|
||||
|
||||
parses: {},
|
||||
@@ -39,9 +35,5 @@ export const config: Config = {
|
||||
},
|
||||
},
|
||||
|
||||
htmls: {
|
||||
patient_address(_rec) {
|
||||
return '-'
|
||||
},
|
||||
},
|
||||
htmls: {},
|
||||
}
|
||||
|
||||
@@ -1,14 +1,39 @@
|
||||
<script setup lang="ts">
|
||||
// Components
|
||||
import PaginationView from '~/components/pub/my-ui/pagination/pagination-view.vue'
|
||||
|
||||
// Types
|
||||
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
|
||||
|
||||
// Configs
|
||||
import { config } from './list-cfg'
|
||||
|
||||
defineProps<{
|
||||
interface Props {
|
||||
data: any[]
|
||||
paginationMeta: PaginationMeta
|
||||
}
|
||||
|
||||
defineProps<Props>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
pageChange: [page: number]
|
||||
}>()
|
||||
|
||||
function handlePageChange(page: number) {
|
||||
emit('pageChange', page)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PubMyUiDataTable
|
||||
v-bind="config"
|
||||
:rows="data"
|
||||
/>
|
||||
<div class="space-y-4">
|
||||
<PubMyUiDataTable
|
||||
v-bind="config"
|
||||
:rows="data"
|
||||
:skeleton-size="paginationMeta?.pageSize"
|
||||
/>
|
||||
<PaginationView
|
||||
:pagination-meta="paginationMeta"
|
||||
@page-change="handlePageChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { Badge } from '~/components/pub/ui/badge'
|
||||
|
||||
const props = defineProps<{
|
||||
rec: any
|
||||
idx?: number
|
||||
}>()
|
||||
|
||||
const doctorStatus = {
|
||||
0: 'Tidak Aktif',
|
||||
1: 'Aktif',
|
||||
}
|
||||
|
||||
const statusText = computed(() => {
|
||||
return doctorStatus[props.rec.status_code as keyof typeof doctorStatus]
|
||||
})
|
||||
|
||||
const badgeVariant = computed(() => {
|
||||
return props.rec.status_code === 1 ? 'default' : 'destructive'
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex justify-center">
|
||||
<Badge :variant="badgeVariant">
|
||||
{{ statusText }}
|
||||
</Badge>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,68 +1,227 @@
|
||||
<script setup lang="ts">
|
||||
import Block from '~/components/pub/my-ui/form/block.vue'
|
||||
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'
|
||||
// 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/Button.vue'
|
||||
|
||||
const props = defineProps<{ modelValue: any }>()
|
||||
const emit = defineEmits(['update:modelValue', 'event'])
|
||||
// Types
|
||||
import type { ItemFormData } from '~/schemas/item.schema'
|
||||
|
||||
const data = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (val) => emit('update:modelValue', val),
|
||||
// Helpers
|
||||
import type z from 'zod'
|
||||
import { useForm } from 'vee-validate'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
|
||||
interface Props {
|
||||
schema: z.ZodSchema<any>
|
||||
itemGroups: any[]
|
||||
uoms: any[]
|
||||
values: any
|
||||
isLoading?: boolean
|
||||
isReadonly?: boolean
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const isShowInfra = false;
|
||||
const isLoading = props.isLoading !== undefined ? props.isLoading : false
|
||||
const isReadonly = props.isReadonly !== undefined ? props.isReadonly : false
|
||||
const emit = defineEmits<{
|
||||
submit: [values: ItemFormData, resetForm: () => void]
|
||||
cancel: [resetForm: () => void]
|
||||
}>()
|
||||
|
||||
const { defineField, errors, meta } = useForm({
|
||||
validationSchema: toTypedSchema(props.schema),
|
||||
initialValues: {
|
||||
code: '',
|
||||
name: '',
|
||||
itemGroup_code: '',
|
||||
uom_code: '',
|
||||
infra_code: '',
|
||||
stock: 0,
|
||||
buyingPrice: 0,
|
||||
sellingPrice: 0,
|
||||
} as Partial<ItemFormData>,
|
||||
})
|
||||
|
||||
const items = [
|
||||
{ value: '1', label: 'item 1' },
|
||||
{ value: '2', label: 'item 2' },
|
||||
{ value: '3', label: 'item 3' },
|
||||
{ value: '4', label: 'item 4' },
|
||||
]
|
||||
const [code, codeAttrs] = defineField('code')
|
||||
const [name, nameAttrs] = defineField('name')
|
||||
const [itemGroup_code, itemGroup_codeAttrs] = defineField('itemGroup_code')
|
||||
const [uom, uomAttrs] = defineField('uom_code')
|
||||
const [infra_code, infra_codeAttrs] = defineField('infra_code')
|
||||
const [stock, stockAttrs] = defineField('stock')
|
||||
const [buyingPrice, buyingPriceAttrs] = defineField('buyingPrice')
|
||||
const [sellingPrice, sellingPriceAttrs] = defineField('sellingPrice')
|
||||
|
||||
if (props.values) {
|
||||
if (props.values.code !== undefined) code.value = props.values.code
|
||||
if (props.values.name !== undefined) name.value = props.values.name
|
||||
if (props.values.itemGroup_code !== undefined) itemGroup_code.value = props.values.itemGroup_code
|
||||
if (props.values.uom_code !== undefined) uom.value = props.values.uom_code
|
||||
if (props.values.infra_code !== undefined) infra_code.value = props.values.infra_code
|
||||
if (props.values.stock !== undefined) stock.value = props.values.stock
|
||||
if (props.values.buyingPrice !== undefined) buyingPrice.value = props.values.buyingPrice
|
||||
if (props.values.sellingPrice !== undefined) sellingPrice.value = props.values.sellingPrice
|
||||
}
|
||||
|
||||
const resetForm = () => {
|
||||
code.value = ''
|
||||
name.value = ''
|
||||
itemGroup_code.value = ''
|
||||
uom.value = ''
|
||||
infra_code.value = ''
|
||||
stock.value = 0
|
||||
buyingPrice.value = 0
|
||||
sellingPrice.value = 0
|
||||
}
|
||||
|
||||
function onSubmitForm() {
|
||||
const formData: ItemFormData = {
|
||||
code: code.value || '',
|
||||
name: name.value || '',
|
||||
itemGroup_code: itemGroup_code.value || '',
|
||||
uom_code: uom.value || '',
|
||||
infra_code: infra_code.value || '',
|
||||
stock: Number(stock.value) || 0,
|
||||
buyingPrice: Number(buyingPrice.value) || 0,
|
||||
sellingPrice: Number(sellingPrice.value) || 0,
|
||||
}
|
||||
emit('submit', formData, resetForm)
|
||||
}
|
||||
|
||||
function onCancelForm() {
|
||||
emit('cancel', resetForm)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form id="entry-form">
|
||||
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg xl:text-xl">
|
||||
<div class="flex flex-col justify-between">
|
||||
<Block>
|
||||
<FieldGroup>
|
||||
<Label>Nama</Label>
|
||||
<Field>
|
||||
<Input v-model="data.name" />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
<FieldGroup>
|
||||
<Label>Kode</Label>
|
||||
<Field>
|
||||
<Input v-model="data.code" />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
<FieldGroup>
|
||||
<Label>Item Group</Label>
|
||||
<Field>
|
||||
<Select :items="items" />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
<FieldGroup>
|
||||
<Label>UOM</Label>
|
||||
<Field>
|
||||
<Select :items="items" />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
<FieldGroup>
|
||||
<Label>Infra</Label>
|
||||
<Field>
|
||||
<Select :items="items" />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
<FieldGroup>
|
||||
<Label>Harga</Label>
|
||||
<Field>
|
||||
<Input v-model="data.price" />
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</Block>
|
||||
</div>
|
||||
<form
|
||||
id="form-item"
|
||||
@submit.prevent
|
||||
>
|
||||
<Block
|
||||
labelSize="thin"
|
||||
class="!mb-2.5 !pt-0 xl:!mb-3"
|
||||
:colCount="1"
|
||||
>
|
||||
<Cell>
|
||||
<Label height="compact">Kode</Label>
|
||||
<Field :errMessage="errors.code">
|
||||
<Input
|
||||
id="code"
|
||||
v-model="code"
|
||||
v-bind="codeAttrs"
|
||||
:disabled="isLoading || isReadonly"
|
||||
/>
|
||||
</Field>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<Label height="compact">Nama</Label>
|
||||
<Field :errMessage="errors.name">
|
||||
<Input
|
||||
id="name"
|
||||
v-model="name"
|
||||
v-bind="nameAttrs"
|
||||
:disabled="isLoading || isReadonly"
|
||||
/>
|
||||
</Field>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<Label height="compact">Item Group</Label>
|
||||
<Field :errMessage="errors.itemGroup_code">
|
||||
<Select
|
||||
id="itemGroup_code"
|
||||
v-model="itemGroup_code"
|
||||
icon-name="i-lucide-chevron-down"
|
||||
placeholder="Pilih Item Group"
|
||||
v-bind="itemGroup_codeAttrs"
|
||||
:items="itemGroups"
|
||||
:disabled="isLoading || isReadonly"
|
||||
/>
|
||||
</Field>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<Label height="compact">UOM</Label>
|
||||
<Field :errMessage="errors.uom_code">
|
||||
<Select
|
||||
id="uom_code"
|
||||
v-model="uom"
|
||||
icon-name="i-lucide-chevron-down"
|
||||
placeholder="Pilih satuan"
|
||||
v-bind="uomAttrs"
|
||||
:items="uoms"
|
||||
:disabled="isLoading || isReadonly"
|
||||
/>
|
||||
</Field>
|
||||
</Cell>
|
||||
<Cell v-if="isShowInfra">
|
||||
<Label height="compact">Infra</Label>
|
||||
<Field :errMessage="errors.infra_code">
|
||||
<Input
|
||||
id="infra_code"
|
||||
v-model="infra_code"
|
||||
v-bind="infra_codeAttrs"
|
||||
:disabled="isLoading || isReadonly"
|
||||
/>
|
||||
</Field>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<Label height="compact">Stok</Label>
|
||||
<Field :errMessage="errors.stock">
|
||||
<Input
|
||||
id="stock"
|
||||
type="number"
|
||||
v-model="stock"
|
||||
v-bind="stockAttrs"
|
||||
:disabled="isLoading || isReadonly"
|
||||
/>
|
||||
</Field>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<Label height="compact">Harga Beli</Label>
|
||||
<Field :errMessage="errors.buyingPrice">
|
||||
<Input
|
||||
id="buyingPrice"
|
||||
type="number"
|
||||
v-model="buyingPrice"
|
||||
v-bind="buyingPriceAttrs"
|
||||
:disabled="isLoading || isReadonly"
|
||||
/>
|
||||
</Field>
|
||||
</Cell>
|
||||
<Cell>
|
||||
<Label height="compact">Harga Jual</Label>
|
||||
<Field :errMessage="errors.sellingPrice">
|
||||
<Input
|
||||
id="sellingPrice"
|
||||
type="number"
|
||||
v-model="sellingPrice"
|
||||
v-bind="sellingPriceAttrs"
|
||||
:disabled="isLoading || isReadonly"
|
||||
/>
|
||||
</Field>
|
||||
</Cell>
|
||||
</Block>
|
||||
<div class="my-2 flex justify-end gap-2 py-2">
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
class="w-[120px]"
|
||||
@click="onCancelForm"
|
||||
>
|
||||
Kembali
|
||||
</Button>
|
||||
<Button
|
||||
v-if="!isReadonly"
|
||||
type="button"
|
||||
class="w-[120px]"
|
||||
:disabled="isLoading || !meta.valid"
|
||||
@click="onSubmitForm"
|
||||
>
|
||||
Simpan
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
@@ -3,30 +3,78 @@ import { defineAsyncComponent } from 'vue'
|
||||
|
||||
const action = defineAsyncComponent(() => import('~/components/pub/my-ui/data/dropdown-action-dud.vue'))
|
||||
|
||||
const _doctorStatus = {
|
||||
0: 'Tidak Aktif',
|
||||
1: 'Aktif',
|
||||
}
|
||||
|
||||
export const config: Config = {
|
||||
cols: [{}, {}, { width: 50 }],
|
||||
cols: [
|
||||
{ width: 100 },
|
||||
{ width: 100 },
|
||||
{ width: 100 },
|
||||
{ width: 100 },
|
||||
{ width: 100 },
|
||||
{ width: 100 },
|
||||
{ width: 100 },
|
||||
{ width: 100 },
|
||||
{ width: 50 },
|
||||
],
|
||||
|
||||
headers: [
|
||||
[
|
||||
{ label: 'Kode' },
|
||||
{ label: 'Nama' },
|
||||
{ label: 'Item Group' },
|
||||
{ label: 'UOM' },
|
||||
{ label: 'Infra' },
|
||||
{ label: 'Stok' },
|
||||
{ label: 'Harga Beli' },
|
||||
{ label: 'Harga Jual' },
|
||||
{ label: 'Aksi' },
|
||||
],
|
||||
],
|
||||
|
||||
keys: ['code', 'name', 'action'],
|
||||
keys: ['code', 'name', 'itemGroup_code', 'uom_code', 'infra_code', 'stock', 'buyingPrice', 'sellingPrice', 'action'],
|
||||
|
||||
delKeyNames: [
|
||||
{ key: 'code', label: 'Kode' },
|
||||
{ key: 'name', label: 'Nama' },
|
||||
],
|
||||
|
||||
parses: {},
|
||||
parses: {
|
||||
itemGroup_code: (rec: unknown): unknown => {
|
||||
const recX = rec as any
|
||||
return recX.itemGroup_code || '-'
|
||||
},
|
||||
uom_code: (rec: unknown): unknown => {
|
||||
const recX = rec as any
|
||||
return recX.uom?.name || '-'
|
||||
},
|
||||
infra_code: (rec: unknown): unknown => {
|
||||
const recX = rec as any
|
||||
return recX.infra_code || '-'
|
||||
},
|
||||
stock: (rec: unknown): unknown => {
|
||||
const recX = rec as any
|
||||
const value = recX.stock
|
||||
if (value === null || value === undefined) {
|
||||
return '-'
|
||||
}
|
||||
return value
|
||||
},
|
||||
buyingPrice: (rec: unknown): unknown => {
|
||||
const recX = rec as any
|
||||
const value = recX.buyingPrice
|
||||
if (value === null || value === undefined) {
|
||||
return '-'
|
||||
}
|
||||
return value
|
||||
},
|
||||
sellingPrice: (rec: unknown): unknown => {
|
||||
const recX = rec as any
|
||||
const value = recX.sellingPrice
|
||||
if (value === null || value === undefined) {
|
||||
return '-'
|
||||
}
|
||||
return value
|
||||
},
|
||||
},
|
||||
|
||||
components: {
|
||||
action(rec, idx) {
|
||||
@@ -39,9 +87,5 @@ export const config: Config = {
|
||||
},
|
||||
},
|
||||
|
||||
htmls: {
|
||||
patient_address(_rec) {
|
||||
return '-'
|
||||
},
|
||||
},
|
||||
htmls: {},
|
||||
}
|
||||
|
||||
@@ -1,14 +1,39 @@
|
||||
<script setup lang="ts">
|
||||
// Components
|
||||
import PaginationView from '~/components/pub/my-ui/pagination/pagination-view.vue'
|
||||
|
||||
// Types
|
||||
import type { PaginationMeta } from '~/components/pub/my-ui/pagination/pagination.type'
|
||||
|
||||
// Configs
|
||||
import { config } from './list-cfg'
|
||||
|
||||
defineProps<{
|
||||
interface Props {
|
||||
data: any[]
|
||||
paginationMeta: PaginationMeta
|
||||
}
|
||||
|
||||
defineProps<Props>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
pageChange: [page: number]
|
||||
}>()
|
||||
|
||||
function handlePageChange(page: number) {
|
||||
emit('pageChange', page)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PubMyUiDataTable
|
||||
v-bind="config"
|
||||
:rows="data"
|
||||
/>
|
||||
<div class="space-y-4">
|
||||
<PubMyUiDataTable
|
||||
v-bind="config"
|
||||
:rows="data"
|
||||
:skeleton-size="paginationMeta?.pageSize"
|
||||
/>
|
||||
<PaginationView
|
||||
:pagination-meta="paginationMeta"
|
||||
@page-change="handlePageChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { Badge } from '~/components/pub/ui/badge'
|
||||
|
||||
const props = defineProps<{
|
||||
rec: any
|
||||
idx?: number
|
||||
}>()
|
||||
|
||||
const doctorStatus = {
|
||||
0: 'Tidak Aktif',
|
||||
1: 'Aktif',
|
||||
}
|
||||
|
||||
const statusText = computed(() => {
|
||||
return doctorStatus[props.rec.status_code as keyof typeof doctorStatus]
|
||||
})
|
||||
|
||||
const badgeVariant = computed(() => {
|
||||
return props.rec.status_code === 1 ? 'default' : 'destructive'
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex justify-center">
|
||||
<Badge :variant="badgeVariant">
|
||||
{{ statusText }}
|
||||
</Badge>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,157 @@
|
||||
<script setup lang="ts">
|
||||
import * as DE from '~/components/pub/my-ui/doc-entry'
|
||||
import * as Cbx from '~/components/pub/my-ui/checkboxes'
|
||||
import { fi } from 'date-fns/locale';
|
||||
|
||||
const substances = [
|
||||
{ value: 'biopsi', label: 'Biopsi'},
|
||||
{ value: 'operation', label: 'Operasi' },
|
||||
{ value: 'scraping', label: 'Kerokan' },
|
||||
{ value: 'cytology', label: 'Sitologi' },
|
||||
{ value: 'fnab', label: 'FNAB' },
|
||||
]
|
||||
|
||||
const fications = [
|
||||
{ value: 'pa-formaline-10p', label: 'pA. Formalin 10%' },
|
||||
{ value: 'spt-alcohol-70p', label: 'Sputum Alkohol 70%' },
|
||||
{ value: 'urn-alcohol-50p', label: 'Urine Alkohol 50%' },
|
||||
{ value: 'vgn-smr-alcohol-95p', label: 'Vagibal Smear 95%' },
|
||||
]
|
||||
|
||||
const prevAp = [
|
||||
{ value: 'clinical', label: 'Klinik' },
|
||||
{ value: 'ro', label: 'RO' },
|
||||
{ value: 'clinical-pat', label: 'Pat. Klinik' },
|
||||
{ value: 'operation', label: 'Operasi' },
|
||||
{ value: 'necropsy', label: 'Nekropsi' },
|
||||
]
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="header">Data Order</div>
|
||||
<DE.Block :col-count="3" :cell-flex="false">
|
||||
<DE.Cell>
|
||||
<DE.Label>Tgl. Order</DE.Label>
|
||||
<DE.Field>
|
||||
<Input readonly />
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
<DE.Cell>
|
||||
<DE.Label>DPJP</DE.Label>
|
||||
<DE.Field>
|
||||
<Input readonly />
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
<DE.Cell>
|
||||
<DE.Label>PPDS</DE.Label>
|
||||
<DE.Field>
|
||||
<Input readonly />
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
</DE.Block>
|
||||
|
||||
<div class="header">Bahan</div>
|
||||
<DE.Block :cell-flex="false">
|
||||
<DE.Cell>
|
||||
<DE.Field>
|
||||
<Cbx.Checkboxes :items="substances" :use-flex="true" />
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
</DE.Block>
|
||||
|
||||
<div class="header">Fiksasi</div>
|
||||
<DE.Block :cell-flex="false">
|
||||
<DE.Cell>
|
||||
<DE.Field>
|
||||
<Cbx.Checkboxes :items="fications" :use-flex="true" />
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
</DE.Block>
|
||||
|
||||
<div class="header">Data Klinis Pasien</div>
|
||||
<DE.Block :col-count="6" :cell-flex="false">
|
||||
<DE.Cell :col-span="3">
|
||||
<DE.Label>Lokalisasi</DE.Label>
|
||||
<DE.Field>
|
||||
<Textarea />
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
<DE.Cell :col-span="3">
|
||||
<DE.Label>Diagnosa Klinik</DE.Label>
|
||||
<DE.Field>
|
||||
<Textarea />
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
<DE.Cell>
|
||||
<DE.Label>Stadium T</DE.Label>
|
||||
<DE.Field>
|
||||
<Input />
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
<DE.Cell>
|
||||
<DE.Label>Stadium M</DE.Label>
|
||||
<DE.Field>
|
||||
<Input />
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
<DE.Cell>
|
||||
<DE.Label>Stadium N</DE.Label>
|
||||
<DE.Field>
|
||||
<Input />
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
<div class=""></div>
|
||||
<DE.Cell :col-span="3">
|
||||
<DE.Label>Keterangan Klinik</DE.Label>
|
||||
<DE.Field>
|
||||
<Textarea rows="2" />
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
</DE.Block>
|
||||
|
||||
<div class="header">Riwayat Pemeriksaan</div>
|
||||
<DE.Block :col-count="2" :cell-flex="false">
|
||||
<DE.Cell>
|
||||
<DE.Label>Riwayat Dulu</DE.Label>
|
||||
<DE.Field>
|
||||
<Textarea />
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
<DE.Cell>
|
||||
<DE.Label>Riwayat Sekarang</DE.Label>
|
||||
<DE.Field>
|
||||
<Textarea />
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
<DE.Cell>
|
||||
<DE.Label>Pemeriksaan PA Sebelumnya</DE.Label>
|
||||
<DE.Field>
|
||||
<Cbx.Checkboxes :items="prevAp" :use-flex="true" />
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
<div></div>
|
||||
<DE.Cell>
|
||||
<DE.Label>Keterangan PA Sebelumnya</DE.Label>
|
||||
<DE.Field>
|
||||
<Textarea />
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
<DE.Cell>
|
||||
<DE.Label>Pemeriksaan Penunjang</DE.Label>
|
||||
<DE.Field>
|
||||
<Textarea />
|
||||
</DE.Field>
|
||||
</DE.Cell>
|
||||
|
||||
</DE.Block>
|
||||
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.header {
|
||||
@apply mb-1.5 text-sm 2xl:text-base font-semibold
|
||||
}
|
||||
</style>
|
||||
+19
-14
@@ -45,6 +45,7 @@ const {
|
||||
handleSearch,
|
||||
fetchData: getItemList,
|
||||
} = usePaginatedList({
|
||||
syncToUrl: false,
|
||||
fetchFn: async (params: any) => {
|
||||
const result = await getList({
|
||||
search: params.search,
|
||||
@@ -102,19 +103,23 @@ onMounted(async () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Dialog v-model:open="isModalOpen" title="" size="xl">
|
||||
<Header
|
||||
v-model="searchInput"
|
||||
:prep="headerPrep"
|
||||
:ref-search-nav="headerPrep.refSearchNav"
|
||||
@search="handleSearch"
|
||||
class=""
|
||||
/>
|
||||
<AppProcedureSrcList
|
||||
:table-config="config"
|
||||
:data="data"
|
||||
:pagination-meta="paginationMeta"
|
||||
@page-change="handlePageChange"
|
||||
/>
|
||||
<Dialog
|
||||
v-model:open="isModalOpen"
|
||||
title=""
|
||||
size="xl"
|
||||
>
|
||||
<Header
|
||||
v-model="searchInput"
|
||||
:prep="headerPrep"
|
||||
:ref-search-nav="headerPrep.refSearchNav"
|
||||
@search="handleSearch"
|
||||
class=""
|
||||
/>
|
||||
<AppProcedureSrcList
|
||||
:table-config="config"
|
||||
:data="data"
|
||||
:pagination-meta="paginationMeta"
|
||||
@page-change="handlePageChange"
|
||||
/>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
+124
-43
@@ -1,62 +1,143 @@
|
||||
<script setup lang="ts">
|
||||
import ProcedureListDialog from './procedure-list.vue'
|
||||
import type { FormErrors } from '~/types/error'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
import { Form } from '~/components/pub/ui/form'
|
||||
import { FieldArray } from 'vee-validate'
|
||||
import InputBase from '~/components/pub/my-ui/form/input-base.vue'
|
||||
import * as DE from '~/components/pub/my-ui/doc-entry'
|
||||
import TextAreaInput from '~/components/pub/my-ui/form/text-area-input.vue'
|
||||
// Types
|
||||
import { type ProcedureSrc } from '~/models/procedure-src'
|
||||
|
||||
// Helper
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
// Components
|
||||
import { FieldArray } from 'vee-validate'
|
||||
import { ButtonAction } from '~/components/pub/my-ui/form'
|
||||
import TableHeader from '~/components/pub/ui/table/TableHeader.vue'
|
||||
import { is } from 'date-fns/locale'
|
||||
import ProcedureListDialog from './procedure-list.vue'
|
||||
|
||||
interface Props {
|
||||
fieldName: string
|
||||
title: string
|
||||
subTitle?: string
|
||||
|
||||
// State UI (Loading / Disabled)
|
||||
isReadonly?: boolean
|
||||
|
||||
// Data Architecture Switch
|
||||
// 'form' = Pakai Vee-Validate (Parent wajib useForm)
|
||||
// 'preview' = Pakai Props sampleItems (Parent bebas)
|
||||
mode?: 'form' | 'preview'
|
||||
|
||||
// Data Source untuk mode 'preview' (atau initial data)
|
||||
sampleItems?: ProcedureSrc[]
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
// Set default mode ke 'form' agar backward compatible
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
mode: 'form',
|
||||
isReadonly: false,
|
||||
sampleItems: () => [],
|
||||
})
|
||||
|
||||
const isProcedurePickerDialogOpen = ref<boolean>(false)
|
||||
provide(`isProcedurePickerDialogOpen`, isProcedurePickerDialogOpen)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="">
|
||||
<div class="mb-2 flex items-center justify-between">
|
||||
<h1 class="mb-2 font-medium">{{ title }}</h1>
|
||||
<Button @click="isProcedurePickerDialogOpen = true" size="xs" variant="outline"
|
||||
class="text-orange-400 border-orange-400 bg-transparent">
|
||||
<Icon name="i-lucide-search" class="h-4 w-4 align-middle transition-colors" />
|
||||
Pilih Diagnosis
|
||||
</Button>
|
||||
<div class="mb-2 flex items-center justify-between">
|
||||
<p class="mb-2 font-medium">{{ title }}</p>
|
||||
|
||||
<ButtonAction
|
||||
v-if="mode === 'form' && !isReadonly"
|
||||
preset="add"
|
||||
title="Tambah Item"
|
||||
icon="i-lucide-search"
|
||||
:label="subTitle || 'Pilih Diagnosis'"
|
||||
:full-width-mobile="true"
|
||||
@click="isProcedurePickerDialogOpen = true"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<FieldArray
|
||||
v-if="mode === 'form'"
|
||||
v-slot="{ fields, push, remove }"
|
||||
:name="props.fieldName"
|
||||
>
|
||||
<ProcedureListDialog :process-fn="push" />
|
||||
|
||||
<div class="overflow-hidden rounded-lg border border-gray-200">
|
||||
<Table>
|
||||
<TableHeader class="bg-gray-100">
|
||||
<TableRow>
|
||||
<TableHead class="w-1/2">Prosedur</TableHead>
|
||||
<TableHead class="w-1/2">ICD-X</TableHead>
|
||||
<TableHead class="w-[50px]">Action</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow v-if="fields.length === 0">
|
||||
<TableCell
|
||||
colspan="3"
|
||||
class="py-4 text-center text-muted-foreground"
|
||||
>
|
||||
Belum ada data dipilih.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
<TableRow
|
||||
v-for="(field, idx) in fields"
|
||||
:key="field.key"
|
||||
>
|
||||
<TableCell :class="cn(isReadonly && 'opacity-50')">
|
||||
{{ (field.value as ProcedureSrc)?.name }}
|
||||
</TableCell>
|
||||
<TableCell :class="cn(isReadonly && 'opacity-50')">
|
||||
{{ (field.value as ProcedureSrc)?.code }}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<ButtonAction
|
||||
v-if="!isReadonly"
|
||||
preset="delete"
|
||||
icon-only
|
||||
:title="`Hapus ${(field.value as ProcedureSrc)?.name}`"
|
||||
@click="remove(idx)"
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</FieldArray>
|
||||
|
||||
<FieldArray v-slot="{ fields, push, remove }" :name="props.fieldName">
|
||||
<ProcedureListDialog :process-fn="push" />
|
||||
<div
|
||||
v-else
|
||||
class="overflow-hidden rounded-lg border border-gray-200"
|
||||
>
|
||||
<Table>
|
||||
<TableHeader class="bg-gray-100">
|
||||
<TableRow>
|
||||
<TableHead class="w-1/2">Prosedur</TableHead>
|
||||
<TableHead class="w-1/2">ICD-X</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow v-if="sampleItems.length === 0">
|
||||
<TableCell
|
||||
colspan="2"
|
||||
class="py-4 text-center text-muted-foreground"
|
||||
>
|
||||
Tidak ada data.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
<div class="border border-gray-200 rounded-lg overflow-hidden">
|
||||
<Table>
|
||||
<TableHeader class="bg-gray-100">
|
||||
<TableRow>
|
||||
<TableHead class="w-1/2">Prosedur</TableHead>
|
||||
<TableHead class="w-1/2">ICD-X</TableHead>
|
||||
<TableHead class="w-1/2">Action</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow v-for="(field, idx) in fields" :key="idx">
|
||||
<TableCell class="">{{ field.value?.name }}</TableCell>
|
||||
<TableCell class="">{{ field.value?.code }}</TableCell>
|
||||
<TableCell class="">
|
||||
<Button type="button" variant="destructive" size="sm" @click="remove(idx)">
|
||||
<Icon name="i-lucide-trash-2" class="h-4 w-4" />
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</FieldArray>
|
||||
<TableRow
|
||||
v-for="item in sampleItems"
|
||||
:key="item.code || item.id"
|
||||
>
|
||||
<TableCell class="text-muted-foreground">
|
||||
{{ item.name }}
|
||||
</TableCell>
|
||||
<TableCell class="text-muted-foreground">
|
||||
{{ item.code }}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
<script setup lang="ts">
|
||||
import Nav from '~/components/pub/my-ui/nav-footer/ba-de-su.vue'
|
||||
import NavOk from '~/components/pub/my-ui/nav-footer/ok.vue'
|
||||
import Header from '~/components/pub/my-ui/nav-header/prep.vue'
|
||||
import Dialog from '~/components/pub/my-ui/modal/dialog.vue'
|
||||
|
||||
import { useQueryCRUDMode } from '~/composables/useQueryCRUD'
|
||||
import { type HeaderPrep } from '~/components/pub/my-ui/data/types'
|
||||
|
||||
// mcu src category
|
||||
import ScrCategorySwitcher from '~/components/app/mcu-src-category/switcher.vue'
|
||||
import { getList as getMcuCategoryList } from '~/services/mcu-src-category.service'
|
||||
|
||||
// mcu src
|
||||
import { type McuSrc } from '~/models/mcu-src'
|
||||
import { getList as getMcuSrcList } from '~/services/mcu-src.service'
|
||||
import McuSrcPicker from '~/components/app/mcu-src/picker-accordion.vue'
|
||||
|
||||
// mcu order
|
||||
import { getDetail } from '~/services/mcu-order.service'
|
||||
import Detail from '~/components/app/mcu-order/detail.vue'
|
||||
|
||||
// mcu order item, manually not using composable
|
||||
import {
|
||||
getList as getMcuOrderItemList,
|
||||
create as createMcuOrderItem,
|
||||
remove as removeMcuOrderItem,
|
||||
} from '~/services/mcu-order-item.service'
|
||||
import { type McuOrderItem } from '~/models/mcu-order-item'
|
||||
import Entry from '~/components/app/mcu-order/entry-for-ap.vue'
|
||||
|
||||
// props
|
||||
const props = defineProps<{
|
||||
encounter_id: number
|
||||
}>()
|
||||
|
||||
// declaration & flows
|
||||
|
||||
// MCU Order
|
||||
const { getQueryParam } = useQueryParam()
|
||||
const id = getQueryParam('id')
|
||||
const dataRes = await getDetail(
|
||||
typeof id === 'string' ? parseInt(id) : 0,
|
||||
{ includes: 'encounter,doctor,doctor-employee,doctor-employee-person' }
|
||||
)
|
||||
const data = dataRes.body?.data
|
||||
|
||||
// MCU Sources
|
||||
const { backToList } = useQueryCRUDMode()
|
||||
|
||||
const headerPrep: HeaderPrep = {
|
||||
title: 'Entry Order LAB PA',
|
||||
icon: 'i-lucide-box',
|
||||
}
|
||||
|
||||
function navClick(type: 'back' | 'delete' | 'draft' | 'submit') {
|
||||
if (type === 'back') {
|
||||
backToList()
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Header
|
||||
:prep="headerPrep"
|
||||
:ref-search-nav="headerPrep.refSearchNav"
|
||||
class="mb-4 xl:mb-5"
|
||||
/>
|
||||
|
||||
<Entry />
|
||||
<Separator class="my-5" />
|
||||
|
||||
<div class="w-full flex justify-center">
|
||||
<Nav @click="navClick" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,159 @@
|
||||
<script setup lang="ts">
|
||||
import Header from '~/components/pub/my-ui/nav-header/prep.vue'
|
||||
|
||||
import { usePaginatedList } from '~/composables/usePaginatedList'
|
||||
import { toast } from '~/components/pub/ui/toast'
|
||||
import RecordConfirmation from '~/components/pub/my-ui/confirmation/record-confirmation.vue'
|
||||
|
||||
import { ActionEvents, type HeaderPrep } from '~/components/pub/my-ui/data/types'
|
||||
|
||||
// Handlers
|
||||
import {
|
||||
recId,
|
||||
recAction,
|
||||
recItem,
|
||||
isReadonly,
|
||||
isFormEntryDialogOpen,
|
||||
isRecordConfirmationOpen,
|
||||
handleActionSave,
|
||||
handleActionRemove,
|
||||
} from '~/handlers/mcu-order.handler'
|
||||
|
||||
// Apps
|
||||
import { getList, getDetail } from '~/services/mcu-order.service'
|
||||
import List from '~/components/app/mcu-order/list.vue'
|
||||
import type { McuOrder } from '~/models/mcu-order'
|
||||
|
||||
const route = useRoute()
|
||||
const { setQueryParams } = useQueryParam()
|
||||
const { crudQueryParams } = useQueryCRUD()
|
||||
|
||||
const title = ref('')
|
||||
|
||||
const plainEid = route.params.id
|
||||
const encounter_id = (plainEid && typeof plainEid == 'string') ? parseInt(plainEid) : 0 // here the
|
||||
|
||||
const {
|
||||
data,
|
||||
isLoading,
|
||||
paginationMeta,
|
||||
searchInput,
|
||||
handlePageChange,
|
||||
handleSearch,
|
||||
fetchData: getMyList,
|
||||
} = usePaginatedList<McuOrder>({
|
||||
fetchFn: async ({ page, search }) => {
|
||||
const result = await getList({
|
||||
search,
|
||||
page,
|
||||
'scope-code': "ap-lab",
|
||||
'encounter-id': encounter_id,
|
||||
includes: 'doctor,doctor-employee,doctor-employee-person',
|
||||
})
|
||||
return { success: result.success || false, body: result.body || {} }
|
||||
},
|
||||
entityName: 'mcu-order'
|
||||
})
|
||||
|
||||
const headerPrep: HeaderPrep = {
|
||||
title: 'Order Lab PA',
|
||||
icon: 'i-lucide-box',
|
||||
refSearchNav: {
|
||||
placeholder: 'Cari (min. 3 karakter)...',
|
||||
minLength: 3,
|
||||
debounceMs: 500,
|
||||
showValidationFeedback: true,
|
||||
onInput: (value: string) => {
|
||||
searchInput.value = value
|
||||
},
|
||||
onClick: () => {},
|
||||
onClear: () => {},
|
||||
},
|
||||
addNav: {
|
||||
label: 'Tambah',
|
||||
icon: 'i-lucide-plus',
|
||||
onClick: () => {
|
||||
recItem.value = null
|
||||
recId.value = 0
|
||||
crudQueryParams.value = { mode: 'entry', recordId: undefined }
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
provide('rec_id', recId)
|
||||
provide('rec_action', recAction)
|
||||
provide('rec_item', recItem)
|
||||
provide('table_data_loader', isLoading)
|
||||
|
||||
const getMyDetail = async (id: number | string) => {
|
||||
const result = await getDetail(id)
|
||||
if (result.success) {
|
||||
const currentValue = result.body?.data || {}
|
||||
recItem.value = currentValue
|
||||
isFormEntryDialogOpen.value = true
|
||||
}
|
||||
}
|
||||
|
||||
// Watch for row actions when recId or recAction changes
|
||||
watch([recId, recAction], () => {
|
||||
switch (recAction.value) {
|
||||
case ActionEvents.showDetail:
|
||||
getMyDetail(recId.value)
|
||||
title.value = 'Detail Order Lab PK'
|
||||
isReadonly.value = true
|
||||
break
|
||||
case ActionEvents.showEdit:
|
||||
getMyDetail(recId.value)
|
||||
title.value = 'Edit Order Lab PK'
|
||||
isReadonly.value = false
|
||||
break
|
||||
case ActionEvents.showConfirmDelete:
|
||||
isRecordConfirmationOpen.value = true
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
watch([isFormEntryDialogOpen], async () => {
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
})
|
||||
|
||||
function cancel(data: McuOrder) {
|
||||
recId.value = data.id
|
||||
recItem.value = data
|
||||
isRecordConfirmationOpen.value = true
|
||||
}
|
||||
|
||||
function edit(data: McuOrder) {
|
||||
setQueryParams({
|
||||
'mode': 'entry',
|
||||
'id': data.id.toString()
|
||||
})
|
||||
recItem.value = data
|
||||
}
|
||||
|
||||
function submit(data: McuOrder) {
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Header :prep="{ ...headerPrep }" />
|
||||
|
||||
<List
|
||||
v-if="!isLoading.dataListLoading"
|
||||
:data="data"
|
||||
:pagination-meta="paginationMeta"
|
||||
@cancel="cancel"
|
||||
@edit="edit"
|
||||
@submit="submit"
|
||||
/>
|
||||
|
||||
<RecordConfirmation
|
||||
v-model:open="isRecordConfirmationOpen"
|
||||
action="delete"
|
||||
:record="recItem"
|
||||
@confirm="() => handleActionRemove(recId, getMyList, toast)"
|
||||
@cancel=""
|
||||
/>
|
||||
</template>
|
||||
@@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
//
|
||||
import List from './list.vue'
|
||||
import Entry from './entry.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
encounter_id: number
|
||||
}>()
|
||||
|
||||
const { mode } = useQueryCRUDMode()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<List v-if="mode === 'list'" :encounter_id="encounter_id" />
|
||||
<Entry v-else :encounter_id="encounter_id" />
|
||||
</template>
|
||||
@@ -188,6 +188,7 @@ onMounted(async () => {
|
||||
@event="handleEvent"
|
||||
@fetch="handleFetch"
|
||||
/>
|
||||
|
||||
<AppViewPatient
|
||||
v-model:open="openPatient"
|
||||
v-model:selected="selectedPatient"
|
||||
@@ -206,11 +207,13 @@ onMounted(async () => {
|
||||
"
|
||||
@save="handleSavePatient"
|
||||
/>
|
||||
|
||||
<AppViewHistory
|
||||
v-model:open="openHistory"
|
||||
:is-action="true"
|
||||
:histories="histories"
|
||||
/>
|
||||
|
||||
<!-- Footer Actions -->
|
||||
<div class="mt-6 flex justify-end gap-2 border-t border-t-slate-300 pt-4">
|
||||
<Button
|
||||
|
||||
@@ -29,6 +29,7 @@ import CpLabOrder from '~/components/content/cp-lab-order/main.vue'
|
||||
import Radiology from '~/components/content/radiology-order/main.vue'
|
||||
import Consultation from '~/components/content/consultation/list.vue'
|
||||
import Cprj from '~/components/content/cprj/entry.vue'
|
||||
import ActionReport from '~/components/content/action-report/entry.vue'
|
||||
import DocUploadList from '~/components/content/document-upload/list.vue'
|
||||
import GeneralConsentList from '~/components/content/general-consent/entry.vue'
|
||||
import SummaryMedic from '~/components/content/summary-medic/entry.vue'
|
||||
@@ -57,7 +58,7 @@ const router = useRouter()
|
||||
const { user, userActiveRole, getActiveRole } = useUserStore()
|
||||
const activeRole = getActiveRole()
|
||||
const activePosition = ref(getServicePosition(activeRole))
|
||||
const menus = ref([] as any)
|
||||
const menus = shallowRef([] as any)
|
||||
const activeMenu = computed({
|
||||
get: () => (route.query?.menu && typeof route.query.menu === 'string' ? route.query.menu : 'status'),
|
||||
set: (value: string) => {
|
||||
@@ -124,6 +125,13 @@ const protocolRows = [
|
||||
{ value: 'resume', label: 'Resume', component: ResumeList, props: { encounter: data } },
|
||||
{ value: 'control', label: 'Surat Kontrol', component: ControlLetterList, props: { encounter: data } },
|
||||
{ value: 'screening', label: 'Skrinning MPP' },
|
||||
{
|
||||
value: 'report',
|
||||
label: 'Laporan Tindakan',
|
||||
groups: ['ambulatory', 'rehabilitation', 'chemotherapy'],
|
||||
component: ActionReport,
|
||||
props: { encounter: data },
|
||||
},
|
||||
{
|
||||
value: 'supporting-document',
|
||||
label: 'Upload Dokumen Pendukung',
|
||||
|
||||
@@ -1,70 +1,198 @@
|
||||
<script setup lang="ts">
|
||||
import type { DataTableLoader } from '~/components/pub/my-ui/data-table/type'
|
||||
import type { HeaderPrep, RefSearchNav } from '~/components/pub/my-ui/data/types'
|
||||
import Modal from '~/components/pub/my-ui/modal/modal.vue'
|
||||
// Components
|
||||
import Dialog from '~/components/pub/my-ui/modal/dialog.vue'
|
||||
import Header from '~/components/pub/my-ui/nav-header/prep.vue'
|
||||
import AppItemList from '~/components/app/item-price/list.vue'
|
||||
import AppItemEntryForm from '~/components/app/item-price/entry-form.vue'
|
||||
import RecordConfirmation from '~/components/pub/my-ui/confirmation/record-confirmation.vue'
|
||||
|
||||
const data = ref([])
|
||||
const entry = ref<any>({})
|
||||
// Constants
|
||||
import { infraGroupCodesKeys } from '~/lib/constants'
|
||||
|
||||
const refSearchNav: RefSearchNav = {
|
||||
onClick: () => {
|
||||
// open filter modal
|
||||
},
|
||||
onInput: (_val: string) => {
|
||||
// filter patient list
|
||||
},
|
||||
onClear: () => {
|
||||
// clear url param
|
||||
},
|
||||
}
|
||||
// Helpers
|
||||
import { usePaginatedList } from '~/composables/usePaginatedList'
|
||||
import { toast } from '~/components/pub/ui/toast'
|
||||
|
||||
// Loading state management
|
||||
const isLoading = reactive<DataTableLoader>({
|
||||
summary: false,
|
||||
isTableLoading: false,
|
||||
// Types
|
||||
import { ActionEvents, type HeaderPrep } from '~/components/pub/my-ui/data/types'
|
||||
import { ItemPriceSchema, type ItemPriceFormData } from '~/schemas/item-price.schema'
|
||||
|
||||
// Handlers
|
||||
import {
|
||||
recId,
|
||||
recAction,
|
||||
recItem,
|
||||
isReadonly,
|
||||
isProcessing,
|
||||
isFormEntryDialogOpen,
|
||||
isRecordConfirmationOpen,
|
||||
onResetState,
|
||||
handleActionSave,
|
||||
handleActionEdit,
|
||||
handleActionRemove,
|
||||
handleCancelForm,
|
||||
} from '~/handlers/item-price.handler'
|
||||
|
||||
// Services
|
||||
import { getList, getDetail } from '~/services/item-price.service'
|
||||
import { getValueLabelList as getItemGroupList } from '~/services/item.service'
|
||||
|
||||
const items = ref<{ value: string | number; label: string }[]>([])
|
||||
const title = ref('')
|
||||
|
||||
const {
|
||||
data,
|
||||
isLoading,
|
||||
paginationMeta,
|
||||
searchInput,
|
||||
handlePageChange,
|
||||
handleSearch,
|
||||
fetchData: getItemList,
|
||||
} = usePaginatedList({
|
||||
fetchFn: async (params: any) => {
|
||||
const result = await getList({
|
||||
search: params.search,
|
||||
sort: 'createdAt:asc',
|
||||
'page-number': params['page-number'] || 0,
|
||||
'page-size': params['page-size'] || 10,
|
||||
})
|
||||
return { success: result.success || false, body: result.body || {} }
|
||||
},
|
||||
entityName: 'item-price',
|
||||
})
|
||||
|
||||
const isOpen = ref(false)
|
||||
|
||||
const recId = ref<number>(0)
|
||||
const recAction = ref<string>('')
|
||||
const recItem = ref<any>(null)
|
||||
|
||||
const hreaderPrep: HeaderPrep = {
|
||||
title: 'Golongan Obat',
|
||||
icon: 'i-lucide-users',
|
||||
const headerPrep: HeaderPrep = {
|
||||
title: 'Harga Item',
|
||||
icon: 'i-lucide-layout-list',
|
||||
refSearchNav: {
|
||||
placeholder: 'Cari (min. 3 karakter)...',
|
||||
minLength: 3,
|
||||
debounceMs: 500,
|
||||
showValidationFeedback: true,
|
||||
onInput: (val: string) => {
|
||||
searchInput.value = val
|
||||
},
|
||||
onClick: () => {},
|
||||
onClear: () => {},
|
||||
},
|
||||
addNav: {
|
||||
label: 'Tambah',
|
||||
onClick: () => (isOpen.value = true),
|
||||
icon: 'i-lucide-plus',
|
||||
onClick: () => {
|
||||
recItem.value = null
|
||||
recId.value = 0
|
||||
isFormEntryDialogOpen.value = true
|
||||
isReadonly.value = false
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
async function getPatientList() {
|
||||
isLoading.isTableLoading = true
|
||||
const resp = await xfetch('/api/v1/medicine-group')
|
||||
if (resp.success) {
|
||||
data.value = (resp.body as Record<string, any>).data
|
||||
}
|
||||
isLoading.isTableLoading = false
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getPatientList()
|
||||
})
|
||||
|
||||
provide('rec_id', recId)
|
||||
provide('rec_action', recAction)
|
||||
provide('rec_item', recItem)
|
||||
provide('table_data_loader', isLoading)
|
||||
|
||||
const getCurrentDetail = async (id: number | string) => {
|
||||
const result = await getDetail(id)
|
||||
if (result.success) {
|
||||
const currentValue = result.body?.data || {}
|
||||
recItem.value = currentValue
|
||||
isFormEntryDialogOpen.value = true
|
||||
}
|
||||
}
|
||||
|
||||
watch([recId, recAction], () => {
|
||||
const currentId = recId.value
|
||||
switch (recAction.value) {
|
||||
case ActionEvents.showDetail:
|
||||
getCurrentDetail(currentId)
|
||||
title.value = 'Detail Harga Item'
|
||||
isReadonly.value = true
|
||||
break
|
||||
case ActionEvents.showEdit:
|
||||
getCurrentDetail(currentId)
|
||||
title.value = 'Edit Harga Item'
|
||||
isReadonly.value = false
|
||||
break
|
||||
case ActionEvents.showConfirmDelete:
|
||||
isRecordConfirmationOpen.value = true
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
items.value = await getItemGroupList()
|
||||
await getItemList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Header :prep="{ ...hreaderPrep }" :ref-search-nav="refSearchNav" />
|
||||
<Header
|
||||
v-model="searchInput"
|
||||
:prep="headerPrep"
|
||||
:ref-search-nav="headerPrep.refSearchNav"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
|
||||
<AppMedicineGroupList :data="data" />
|
||||
<AppItemList
|
||||
:data="data"
|
||||
:pagination-meta="paginationMeta"
|
||||
@page-change="handlePageChange"
|
||||
/>
|
||||
|
||||
<Modal v-model:open="isOpen" title="Tambah Golongan Obat" size="lg" prevent-outside>
|
||||
<AppMedicineGroupEntryForm v-model="entry" />
|
||||
</Modal>
|
||||
<Dialog
|
||||
v-model:open="isFormEntryDialogOpen"
|
||||
:title="!!recItem ? title : 'Tambah Harga Item'"
|
||||
size="lg"
|
||||
prevent-outside
|
||||
@update:open="
|
||||
(value: any) => {
|
||||
onResetState()
|
||||
isFormEntryDialogOpen = value
|
||||
}
|
||||
"
|
||||
>
|
||||
<AppItemEntryForm
|
||||
:schema="ItemPriceSchema"
|
||||
:items="items"
|
||||
:values="recItem"
|
||||
:is-loading="isProcessing"
|
||||
:is-readonly="isReadonly"
|
||||
@submit="
|
||||
(values: ItemPriceFormData | Record<string, any>, resetForm: () => void) => {
|
||||
if (recId > 0) {
|
||||
handleActionEdit(recId, values, getItemList, resetForm, toast)
|
||||
return
|
||||
}
|
||||
handleActionSave(values, getItemList, resetForm, toast)
|
||||
}
|
||||
"
|
||||
@cancel="handleCancelForm"
|
||||
/>
|
||||
</Dialog>
|
||||
|
||||
<RecordConfirmation
|
||||
v-model:open="isRecordConfirmationOpen"
|
||||
action="delete"
|
||||
:record="recItem"
|
||||
@confirm="() => handleActionRemove(recId, getItemList, toast)"
|
||||
@cancel=""
|
||||
>
|
||||
<template #default="{ record }">
|
||||
<div class="text-sm">
|
||||
<p>
|
||||
<strong>ID:</strong>
|
||||
{{ record?.id }}
|
||||
</p>
|
||||
<p v-if="record?.name">
|
||||
<strong>Nama:</strong>
|
||||
{{ record.name }}
|
||||
</p>
|
||||
<p v-if="record?.code">
|
||||
<strong>Kode:</strong>
|
||||
{{ record.code }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
</RecordConfirmation>
|
||||
</template>
|
||||
|
||||
@@ -1,70 +1,208 @@
|
||||
<script setup lang="ts">
|
||||
import type { DataTableLoader } from '~/components/pub/my-ui/data-table/type'
|
||||
import type { HeaderPrep, RefSearchNav } from '~/components/pub/my-ui/data/types'
|
||||
import Modal from '~/components/pub/my-ui/modal/modal.vue'
|
||||
// Components
|
||||
import Dialog from '~/components/pub/my-ui/modal/dialog.vue'
|
||||
import Header from '~/components/pub/my-ui/nav-header/prep.vue'
|
||||
import AppItemList from '~/components/app/item/list.vue'
|
||||
import AppItemEntryForm from '~/components/app/item/entry-form.vue'
|
||||
import RecordConfirmation from '~/components/pub/my-ui/confirmation/record-confirmation.vue'
|
||||
|
||||
const data = ref([])
|
||||
const entry = ref<any>({})
|
||||
// Constants
|
||||
import { itemGroupCodes } from '~/lib/constants'
|
||||
|
||||
const refSearchNav: RefSearchNav = {
|
||||
onClick: () => {
|
||||
// open filter modal
|
||||
},
|
||||
onInput: (_val: string) => {
|
||||
// filter patient list
|
||||
},
|
||||
onClear: () => {
|
||||
// clear url param
|
||||
},
|
||||
}
|
||||
// Helpers
|
||||
import { usePaginatedList } from '~/composables/usePaginatedList'
|
||||
import { toast } from '~/components/pub/ui/toast'
|
||||
|
||||
// Loading state management
|
||||
const isLoading = reactive<DataTableLoader>({
|
||||
summary: false,
|
||||
isTableLoading: false,
|
||||
// Types
|
||||
import { ActionEvents, type HeaderPrep } from '~/components/pub/my-ui/data/types'
|
||||
import { ItemSchema, type ItemFormData } from '~/schemas/item.schema'
|
||||
|
||||
// Handlers
|
||||
import {
|
||||
recId,
|
||||
recAction,
|
||||
recItem,
|
||||
isReadonly,
|
||||
isProcessing,
|
||||
isFormEntryDialogOpen,
|
||||
isRecordConfirmationOpen,
|
||||
onResetState,
|
||||
handleActionSave,
|
||||
handleActionEdit,
|
||||
handleActionRemove,
|
||||
handleCancelForm,
|
||||
} from '~/handlers/item.handler'
|
||||
|
||||
// Services
|
||||
import { getList, getDetail } from '~/services/item.service'
|
||||
import { getValueLabelList as getUomList } from '~/services/uom.service'
|
||||
|
||||
const itemGroups = ref<{ value: string | number; label: string }[]>([])
|
||||
const uoms = ref<{ value: string | number; label: string }[]>([])
|
||||
const title = ref('')
|
||||
|
||||
const {
|
||||
data,
|
||||
isLoading,
|
||||
paginationMeta,
|
||||
searchInput,
|
||||
handlePageChange,
|
||||
handleSearch,
|
||||
fetchData: getItemList,
|
||||
} = usePaginatedList({
|
||||
fetchFn: async (params: any) => {
|
||||
const result = await getList({
|
||||
search: params.search,
|
||||
sort: 'createdAt:asc',
|
||||
'page-number': params['page-number'] || 0,
|
||||
'page-size': params['page-size'] || 10,
|
||||
includes: 'uom',
|
||||
})
|
||||
return { success: result.success || false, body: result.body || {} }
|
||||
},
|
||||
entityName: 'item',
|
||||
})
|
||||
|
||||
const isOpen = ref(false)
|
||||
|
||||
const recId = ref<number>(0)
|
||||
const recAction = ref<string>('')
|
||||
const recItem = ref<any>(null)
|
||||
|
||||
const hreaderPrep: HeaderPrep = {
|
||||
const headerPrep: HeaderPrep = {
|
||||
title: 'Item',
|
||||
icon: 'i-lucide-users',
|
||||
icon: 'i-lucide-layout-list',
|
||||
refSearchNav: {
|
||||
placeholder: 'Cari (min. 3 karakter)...',
|
||||
minLength: 3,
|
||||
debounceMs: 500,
|
||||
showValidationFeedback: true,
|
||||
onInput: (val: string) => {
|
||||
searchInput.value = val
|
||||
},
|
||||
onClick: () => {},
|
||||
onClear: () => {},
|
||||
},
|
||||
addNav: {
|
||||
label: 'Tambah',
|
||||
onClick: () => (isOpen.value = true),
|
||||
icon: 'i-lucide-plus',
|
||||
onClick: () => {
|
||||
recItem.value = null
|
||||
recId.value = 0
|
||||
isFormEntryDialogOpen.value = true
|
||||
isReadonly.value = false
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
async function getPatientList() {
|
||||
isLoading.isTableLoading = true
|
||||
const resp = await xfetch('/api/v1/medicine-group')
|
||||
if (resp.success) {
|
||||
data.value = (resp.body as Record<string, any>).data
|
||||
}
|
||||
isLoading.isTableLoading = false
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getPatientList()
|
||||
})
|
||||
|
||||
provide('rec_id', recId)
|
||||
provide('rec_action', recAction)
|
||||
provide('rec_item', recItem)
|
||||
provide('table_data_loader', isLoading)
|
||||
|
||||
const getCurrentDetail = async (id: number | string) => {
|
||||
const result = await getDetail(id)
|
||||
if (result.success) {
|
||||
const currentValue = result.body?.data || {}
|
||||
recItem.value = currentValue
|
||||
isFormEntryDialogOpen.value = true
|
||||
}
|
||||
}
|
||||
|
||||
watch([recId, recAction], () => {
|
||||
const currentId = recItem.value?.code ? recItem.value.code : recId.value
|
||||
switch (recAction.value) {
|
||||
case ActionEvents.showDetail:
|
||||
getCurrentDetail(currentId)
|
||||
title.value = 'Detail Item'
|
||||
isReadonly.value = true
|
||||
break
|
||||
case ActionEvents.showEdit:
|
||||
getCurrentDetail(currentId)
|
||||
title.value = 'Edit Item'
|
||||
isReadonly.value = false
|
||||
break
|
||||
case ActionEvents.showConfirmDelete:
|
||||
isRecordConfirmationOpen.value = true
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
itemGroups.value = Object.keys(itemGroupCodes).map((key) => ({
|
||||
value: key,
|
||||
label: itemGroupCodes[key],
|
||||
})) as any
|
||||
uoms.value = await getUomList()
|
||||
await getItemList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Header :prep="{ ...hreaderPrep }" :ref-search-nav="refSearchNav" />
|
||||
<Header
|
||||
v-model="searchInput"
|
||||
:prep="headerPrep"
|
||||
:ref-search-nav="headerPrep.refSearchNav"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
|
||||
<AppItemList :data="data" />
|
||||
<AppItemList
|
||||
:data="data"
|
||||
:pagination-meta="paginationMeta"
|
||||
@page-change="handlePageChange"
|
||||
/>
|
||||
|
||||
<Modal v-model:open="isOpen" title="Tambah Golongan Obat" size="xl" prevent-outside>
|
||||
<AppItemEntryForm v-model="entry" />
|
||||
</Modal>
|
||||
<Dialog
|
||||
v-model:open="isFormEntryDialogOpen"
|
||||
:title="!!recItem ? title : 'Tambah Item'"
|
||||
size="lg"
|
||||
prevent-outside
|
||||
@update:open="
|
||||
(value: any) => {
|
||||
onResetState()
|
||||
isFormEntryDialogOpen = value
|
||||
}
|
||||
"
|
||||
>
|
||||
<AppItemEntryForm
|
||||
:schema="ItemSchema"
|
||||
:values="recItem"
|
||||
:item-groups="itemGroups"
|
||||
:uoms="uoms"
|
||||
:is-loading="isProcessing"
|
||||
:is-readonly="isReadonly"
|
||||
@submit="
|
||||
(values: ItemFormData | Record<string, any>, resetForm: () => void) => {
|
||||
if (recId > 0) {
|
||||
handleActionEdit(recItem?.code ? recItem.code : recId, values, () => {
|
||||
getItemList()
|
||||
onResetState()
|
||||
}, resetForm, toast)
|
||||
return
|
||||
}
|
||||
handleActionSave(values, getItemList, resetForm, toast)
|
||||
}
|
||||
"
|
||||
@cancel="handleCancelForm"
|
||||
/>
|
||||
</Dialog>
|
||||
|
||||
<RecordConfirmation
|
||||
v-model:open="isRecordConfirmationOpen"
|
||||
action="delete"
|
||||
:record="recItem"
|
||||
@confirm="() => handleActionRemove(recItem?.code ? recItem.code : recId, getItemList, toast)"
|
||||
@cancel=""
|
||||
>
|
||||
<template #default="{ record }">
|
||||
<div class="text-sm">
|
||||
<p>
|
||||
<strong>ID:</strong>
|
||||
{{ record?.id }}
|
||||
</p>
|
||||
<p v-if="record?.name">
|
||||
<strong>Nama:</strong>
|
||||
{{ record.name }}
|
||||
</p>
|
||||
<p v-if="record?.code">
|
||||
<strong>Kode:</strong>
|
||||
{{ record.code }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
</RecordConfirmation>
|
||||
</template>
|
||||
|
||||
@@ -158,7 +158,7 @@ export const permissions: Record<string, Record<string, Permission[]>> = {
|
||||
'emp|lab': ['R'],
|
||||
'emp|rad': ['R'],
|
||||
},
|
||||
'/ambulatory/encounter/[id]/process?menu=pa-lab-order': {
|
||||
'/ambulatory/encounter/[id]/process?menu=ap-lab-order': {
|
||||
'emp|doc': ['R', 'U'],
|
||||
'emp|nur': ['R'],
|
||||
'emp|thr': ['R'],
|
||||
|
||||
@@ -75,6 +75,7 @@ export function useEncounterEntry(props: {
|
||||
const isSaveDisabled = computed(() => {
|
||||
return !selectedPatient.value || !selectedPatientObject.value || isSaving.value || isLoadingDetail.value
|
||||
})
|
||||
const isUsePaymentNew = true
|
||||
|
||||
function getListPath(): string {
|
||||
if (props.classCode === 'ambulatory') {
|
||||
@@ -436,7 +437,7 @@ export function useEncounterEntry(props: {
|
||||
formData.registerDate = date.toISOString().split('T')[0]
|
||||
}
|
||||
|
||||
if (encounter.paymentMethod_code) {
|
||||
if (!isUsePaymentNew && encounter.paymentMethod_code) {
|
||||
formData.paymentMethodCode = encounter.paymentMethod_code
|
||||
if (encounter.paymentMethod_code === 'insurance') {
|
||||
formData.paymentType = 'jkn'
|
||||
@@ -449,7 +450,7 @@ export function useEncounterEntry(props: {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
formData.paymentType = 'spm'
|
||||
formData.paymentType = encounter.paymentMethod_code
|
||||
}
|
||||
|
||||
formData.cardNumber = encounter.member_number || ''
|
||||
@@ -520,7 +521,7 @@ export function useEncounterEntry(props: {
|
||||
sippFile.value = formValues.sippFile || null
|
||||
|
||||
let paymentMethodCode = formValues.paymentMethod_code ?? null
|
||||
if (!paymentMethodCode) {
|
||||
if (!isUsePaymentNew && !paymentMethodCode) {
|
||||
if (formValues.paymentType === 'jkn' || formValues.paymentType === 'jkmm') {
|
||||
paymentMethodCode = 'insurance'
|
||||
} else if (formValues.paymentType === 'spm') {
|
||||
|
||||
@@ -42,6 +42,7 @@ const PrescriptionAsync = defineAsyncComponent(() => import('~/components/conten
|
||||
const CpLabOrderAsync = defineAsyncComponent(() => import('~/components/content/cp-lab-order/main.vue'))
|
||||
const ProcedureRoomOrderAsync = defineAsyncComponent(() => import('~/components/content/procedure-room-order/main.vue'))
|
||||
const MicroLabOrderAsync = defineAsyncComponent(() => import('~/components/content/micro-lab-order/main.vue'))
|
||||
const ApLabOrderAsync = defineAsyncComponent(() => import('~/components/content/ap-lab-order/main.vue'))
|
||||
const CprjAsync = defineAsyncComponent(() => import('~/components/content/cprj/entry.vue'))
|
||||
const RadiologyAsync = defineAsyncComponent(() => import('~/components/content/radiology-order/main.vue'))
|
||||
const ConsultationAsync = defineAsyncComponent(() => import('~/components/content/consultation/list.vue'))
|
||||
@@ -57,6 +58,8 @@ const InitialNursingStudyAsync = defineAsyncComponent(() => import('~/components
|
||||
const AssessmentEducationEntryAsync = defineAsyncComponent(
|
||||
() => import('~/components/content/assessment-education/entry.vue'),
|
||||
)
|
||||
const SummaryMedicAsync = defineAsyncComponent(() => import('~/components/content/summary-medic/entry.vue'))
|
||||
const ActionReportEntryAsync = defineAsyncComponent(() => import('~/components/content/action-report/entry.vue'))
|
||||
|
||||
const defaultKeys: Record<string, any> = {
|
||||
status: {
|
||||
@@ -166,8 +169,8 @@ const defaultKeys: Record<string, any> = {
|
||||
classCode: ['ambulatory', 'emergency', 'inpatient'],
|
||||
unit: 'all',
|
||||
},
|
||||
paLabOrder: {
|
||||
id: 'pa-lab-order',
|
||||
apLabOrder: {
|
||||
id: 'ap-lab-order',
|
||||
title: 'Order Lab PA',
|
||||
classCode: ['ambulatory', 'emergency', 'inpatient'],
|
||||
unit: 'all',
|
||||
@@ -388,10 +391,10 @@ export function injectComponents(id: string | number, data: EncounterListData, m
|
||||
currentKeys.microLabOrder['component'] = MicroLabOrderAsync
|
||||
currentKeys.microLabOrder['props'] = { encounter_id: id }
|
||||
}
|
||||
if (currentKeys?.paLabOrder) {
|
||||
// TODO: add component for paLabOrder
|
||||
currentKeys.paLabOrder['component'] = null
|
||||
currentKeys.paLabOrder['props'] = { encounter_id: id }
|
||||
if (currentKeys?.apLabOrder) {
|
||||
// TODO: add component for apLabOrder
|
||||
currentKeys.apLabOrder['component'] = ApLabOrderAsync
|
||||
currentKeys.apLabOrder['props'] = { encounter_id: id }
|
||||
}
|
||||
if (currentKeys?.procedureRoomOrder) {
|
||||
currentKeys.procedureRoomOrder['component'] = ProcedureRoomOrderAsync
|
||||
@@ -455,6 +458,19 @@ export function injectComponents(id: string | number, data: EncounterListData, m
|
||||
currentKeys.initialNursingStudy['props'] = { encounter: data?.encounter }
|
||||
}
|
||||
|
||||
if (currentKeys?.initialNursingStudy) {
|
||||
currentKeys.initialNursingStudy['component'] = InitialNursingStudyAsync
|
||||
currentKeys.initialNursingStudy['props'] = { encounter: data?.encounter }
|
||||
}
|
||||
|
||||
if (currentKeys?.actionReport) {
|
||||
currentKeys.actionReport['component'] = ActionReportEntryAsync
|
||||
currentKeys.actionReport['props'] = {
|
||||
encounter: data?.encounter,
|
||||
type: 'action-report',
|
||||
label: currentKeys.actionReport['title'],
|
||||
}
|
||||
}
|
||||
return currentKeys
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,587 @@
|
||||
import { isValidDate } from '~/lib/date'
|
||||
import { medicalRoles } from '~/const/common/role'
|
||||
|
||||
export interface EncounterItem {
|
||||
id: string
|
||||
title: string
|
||||
classCode?: string[]
|
||||
unit?: string
|
||||
afterId?: string
|
||||
component?: any
|
||||
props?: Record<string, any>
|
||||
}
|
||||
|
||||
export interface EncounterProps {
|
||||
classCode: 'ambulatory' | 'emergency' | 'inpatient' | 'outpatient'
|
||||
subClassCode: 'reg' | 'rehab' | 'chemo' | 'emg' | 'eon' | 'op' | 'icu' | 'hcu' | 'vk'
|
||||
}
|
||||
|
||||
export interface EncounterListData {
|
||||
encounter?: any
|
||||
status?: any
|
||||
medicalAssessment?: any
|
||||
medicalAssessmentRehab: any
|
||||
functionAssessment?: any
|
||||
protocolTheraphy?: any
|
||||
protocolChemotherapy?: any
|
||||
medicineProtocolChemotherapy?: any
|
||||
consultation?: any
|
||||
letterOfControl?: any
|
||||
}
|
||||
|
||||
const StatusAsync = defineAsyncComponent(() => import('~/components/content/encounter/status.vue'))
|
||||
const EarlyMedicalRehabListAsync = defineAsyncComponent(() => import('~/components/content/soapi/entry.vue'))
|
||||
const EarlyMedicalAssesmentListAsync = defineAsyncComponent(() => import('~/components/content/soapi/entry.vue'))
|
||||
const InitialNursingStudyAsync = defineAsyncComponent(() => import('~/components/content/initial-nursing/entry.vue'))
|
||||
// const AssesmentFunctionListAsync = defineAsyncComponent(() => import('~/components/content/soapi/entry.vue'))
|
||||
const KfrListAsync = defineAsyncComponent(() => import('~/components/content/kfr/main.vue'))
|
||||
const ChemoProtocolListAsync = defineAsyncComponent(() => import('~/components/app/chemotherapy/list.protocol.vue'))
|
||||
const ChemoMedicineProtocolListAsync = defineAsyncComponent(() => import('~/components/app/chemotherapy/list.medicine.vue'))
|
||||
const GeneralConsentListAsync = defineAsyncComponent(() => import('~/components/content/general-consent/entry.vue'))
|
||||
const CprjAsync = defineAsyncComponent(() => import('~/components/content/cprj/entry.vue'))
|
||||
|
||||
const PrescriptionAsync = defineAsyncComponent(() => import('~/components/content/prescription/main.vue'))
|
||||
const DeviceOrderAsync = defineAsyncComponent(() => import('~/components/content/device-order/main.vue'))
|
||||
const RadiologyOrderAsync = defineAsyncComponent(() => import('~/components/content/radiology-order/main.vue'))
|
||||
const CpLabOrderAsync = defineAsyncComponent(() => import('~/components/content/cp-lab-order/main.vue'))
|
||||
const MicroLabOrderAsync = defineAsyncComponent(() => import('~/components/content/micro-lab-order/main.vue'))
|
||||
const ApLabOrderAsync = defineAsyncComponent(() => import('~/components/content/ap-lab-order/main.vue'))
|
||||
const ProcedureRoomOrderAsync = defineAsyncComponent(() => import('~/components/content/procedure-room-order/main.vue'))
|
||||
|
||||
const ConsultationAsync = defineAsyncComponent(() => import('~/components/content/consultation/list.vue'))
|
||||
const DocUploadListAsync = defineAsyncComponent(() => import('~/components/content/document-upload/main.vue'))
|
||||
const ResumeListAsync = defineAsyncComponent(() => import('~/components/content/resume/main.vue'))
|
||||
const ControlLetterListAsync = defineAsyncComponent(() => import('~/components/content/control-letter/main.vue'))
|
||||
const PrbListAsync = defineAsyncComponent(() => import('~/components/content/prb/main.vue'))
|
||||
const SurgeryReportListAsync = defineAsyncComponent(() => import('~/components/content/surgery-report/main.vue'))
|
||||
const VaccineDataListAsync = defineAsyncComponent(() => import('~/components/content/vaccine-data/main.vue'))
|
||||
|
||||
const defaultKeys: Record<string, any> = {
|
||||
status: {
|
||||
id: 'status',
|
||||
title: 'Status Masuk/Keluar',
|
||||
classCode: ['ambulatory', 'emergency', 'inpatient'],
|
||||
unit: 'all',
|
||||
},
|
||||
earlyMedicalRehabAssessment: {
|
||||
id: 'rehab-medical-assessment',
|
||||
title: 'Pengkajian Awal Medis Rehabilitasi Medis',
|
||||
classCode: ['ambulatory'],
|
||||
unit: 'rehab',
|
||||
},
|
||||
earlyMedicalAssessment: {
|
||||
id: 'early-medical-assessment',
|
||||
title: 'Pengkajian Awal Medis',
|
||||
classCode: ['ambulatory', 'emergency', 'inpatient'],
|
||||
unit: 'all',
|
||||
afterId: 'early-medical-assessment',
|
||||
},
|
||||
initialNursingStudy: {
|
||||
id: 'initial-nursing-study',
|
||||
title: 'Kajian Awal Keperawatan',
|
||||
classCode: ['ambulatory', 'emergency', 'inpatient'],
|
||||
unit: 'all',
|
||||
},
|
||||
fkr: {
|
||||
id: 'fkr',
|
||||
title: 'FKR',
|
||||
classCode: ['ambulatory', 'emergency', 'inpatient'],
|
||||
unit: 'rehab',
|
||||
},
|
||||
chemotherapyProtocol: {
|
||||
id: 'chemotherapy-protocol',
|
||||
title: 'Protokol Kemoterapi',
|
||||
classCode: ['ambulatory'],
|
||||
unit: 'chemo',
|
||||
},
|
||||
chemotherapyMedicine: {
|
||||
id: 'chemotherapy-medicine',
|
||||
title: 'Protokol Obat Kemoterapi',
|
||||
classCode: ['ambulatory'],
|
||||
unit: 'chemo',
|
||||
},
|
||||
educationAssessment: {
|
||||
id: 'education-assessment',
|
||||
title: 'Asesmen Kebutuhan Edukasi',
|
||||
classCode: ['ambulatory', 'emergency', 'inpatient'],
|
||||
unit: 'all',
|
||||
},
|
||||
|
||||
// NOTE: Replaced by FRK
|
||||
// functionAssessment: {
|
||||
// id: 'function-assessment',
|
||||
// title: 'Asesmen Fungsi',
|
||||
// classCode: ['ambulatory'],
|
||||
// unit: 'rehab',
|
||||
// afterId: 'rehab-medical-assessment',
|
||||
// },
|
||||
// therapyProtocol: {
|
||||
// id: 'therapy-protocol',
|
||||
// classCode: ['ambulatory'],
|
||||
// title: 'Protokol Terapi',
|
||||
// unit: 'rehab',
|
||||
// afterId: 'function-assessment',
|
||||
// },
|
||||
generalConsent: {
|
||||
id: 'general-consent',
|
||||
title: 'General Consent',
|
||||
classCode: ['ambulatory', 'emergency', 'inpatient'],
|
||||
unit: 'all',
|
||||
},
|
||||
patientAmbNote: { // CPRJ
|
||||
id: 'patient-amb-note',
|
||||
title: 'CPRJ',
|
||||
classCode: ['ambulatory', 'emergency'],
|
||||
unit: 'ambulatory',
|
||||
},
|
||||
patientDevNote: { // CPPT
|
||||
id: 'patient-dev-note',
|
||||
title: 'CPP',
|
||||
classCode: ['inpatient'],
|
||||
unit: 'inpatient',
|
||||
},
|
||||
|
||||
prescription: {
|
||||
id: 'prescription',
|
||||
title: 'Order Obat',
|
||||
classCode: ['ambulatory', 'emergency', 'inpatient'],
|
||||
unit: 'all',
|
||||
},
|
||||
deviceOrder: {
|
||||
id: 'device-order',
|
||||
title: 'Order Alkes',
|
||||
classCode: ['ambulatory', 'emergency', 'inpatient'],
|
||||
unit: 'all',
|
||||
},
|
||||
radiologyOrder: {
|
||||
id: 'radiology-order',
|
||||
title: 'Order Radiologi',
|
||||
classCode: ['ambulatory', 'emergency', 'inpatient'],
|
||||
unit: 'all',
|
||||
},
|
||||
cpLabOrder: {
|
||||
id: 'cp-lab-order',
|
||||
title: 'Order Lab PK',
|
||||
classCode: ['ambulatory', 'emergency', 'inpatient'],
|
||||
unit: 'all',
|
||||
},
|
||||
microLabOrder: {
|
||||
id: 'micro-lab-order',
|
||||
title: 'Order Lab Mikro',
|
||||
classCode: ['ambulatory', 'emergency', 'inpatient'],
|
||||
unit: 'all',
|
||||
},
|
||||
paLabOrder: {
|
||||
id: 'pa-lab-order',
|
||||
title: 'Order Lab PA',
|
||||
classCode: ['ambulatory', 'emergency', 'inpatient'],
|
||||
unit: 'all',
|
||||
},
|
||||
procedureRoomOrder: {
|
||||
id: 'procedure-room-order',
|
||||
title: 'Order Ruang Tindakan',
|
||||
classCode: ['ambulatory', 'emergency', 'inpatient'],
|
||||
unit: 'all',
|
||||
},
|
||||
mcuResult: {
|
||||
id: 'mcu-result',
|
||||
title: 'Hasil Penunjang',
|
||||
classCode: ['ambulatory', 'emergency', 'inpatient'],
|
||||
unit: 'all',
|
||||
},
|
||||
procedureReport: {
|
||||
id: 'action-report',
|
||||
title: 'Laporan Tindakan',
|
||||
classCode: ['ambulatory', 'emergency', 'inpatient'],
|
||||
unit: 'all',
|
||||
},
|
||||
operationReport: {
|
||||
id: 'action-report',
|
||||
title: 'Laporan Tindakan',
|
||||
classCode: ['ambulatory', 'emergency', 'inpatient'],
|
||||
unit: 'all',
|
||||
},
|
||||
ambulanceOrder: {
|
||||
id: 'ambulance-order',
|
||||
title: 'Order Ambulans',
|
||||
classCode: ['ambulatory', 'emergency'],
|
||||
unit: 'all',
|
||||
},
|
||||
surgeryReport: {
|
||||
id: 'surgery-report',
|
||||
title: 'Laporan Operasi',
|
||||
classCode: ['ambulatory', 'emergency', 'inpatient'],
|
||||
unit: 'all',
|
||||
},
|
||||
forensic: {
|
||||
id: 'surgery-report',
|
||||
title: 'Laporan Operasi',
|
||||
classCode: ['ambulatory', 'emergency', 'inpatient'],
|
||||
unit: 'all',
|
||||
},
|
||||
consultation: {
|
||||
id: 'consultation',
|
||||
title: 'Konsultasi',
|
||||
classCode: ['ambulatory', 'emergency', 'inpatient'],
|
||||
unit: 'all',
|
||||
},
|
||||
resume: {
|
||||
id: 'resume',
|
||||
title: 'Resume Medis',
|
||||
classCode: ['ambulatory', 'emergency', 'inpatient'],
|
||||
unit: 'all',
|
||||
},
|
||||
controlLetter: {
|
||||
id: 'control-letter',
|
||||
title: 'Surat Kontrol',
|
||||
classCode: ['ambulatory', 'emergency', 'inpatient'],
|
||||
unit: 'all',
|
||||
},
|
||||
inpatientLetter: {
|
||||
id: 'inpatient-letter',
|
||||
title: 'SPRI',
|
||||
classCode: ['ambulatory', 'emergency'],
|
||||
unit: 'all',
|
||||
},
|
||||
refBack: {
|
||||
id: 'reference-back',
|
||||
title: 'PRB',
|
||||
classCode: ['ambulatory', 'emergency', 'inpatient'],
|
||||
unit: 'all',
|
||||
},
|
||||
screening: {
|
||||
id: 'screening',
|
||||
title: 'Skrinning MPP',
|
||||
classCode: ['ambulatory', 'emergency', 'inpatient'],
|
||||
unit: 'all',
|
||||
},
|
||||
supportingDocument: {
|
||||
id: 'supporting-document',
|
||||
title: 'Upload Dokumen Pendukung',
|
||||
classCode: ['ambulatory'],
|
||||
unit: 'all',
|
||||
},
|
||||
vaccineData: {
|
||||
id: 'vaccine-data',
|
||||
title: 'Data Vaksin',
|
||||
classCode: ['ambulatory', 'emergency', 'inpatient'],
|
||||
unit: 'all',
|
||||
},
|
||||
priceList: {
|
||||
id: 'price-list',
|
||||
title: 'Tarif Tindakan',
|
||||
classCode: ['ambulatory', 'emergency', 'inpatient'],
|
||||
unit: 'all',
|
||||
},
|
||||
}
|
||||
|
||||
export function getItemsByClassCode(classCode: string, items: EncounterItem[]) {
|
||||
return items.filter((item) => item.classCode?.includes(classCode))
|
||||
}
|
||||
|
||||
export function getItemsByUnit(unit: string, items: EncounterItem[]) {
|
||||
return items.filter((item) => item.unit === unit)
|
||||
}
|
||||
|
||||
export function getItemsByIds(ids: string[], items: EncounterItem[]) {
|
||||
return items.filter((item) => ids.includes(item.id))
|
||||
}
|
||||
|
||||
export function getIndexById(id: string, items: EncounterItem[]) {
|
||||
return items.findIndex((item) => item.id === id)
|
||||
}
|
||||
|
||||
export const getItemsAll = (classCode: string, unit: string, items: EncounterItem[]) => {
|
||||
const prevItems = [...items]
|
||||
let updateItems = getItemsByClassCode(classCode, prevItems)
|
||||
updateItems = getItemsByUnit(unit, updateItems)
|
||||
return updateItems
|
||||
}
|
||||
|
||||
export function insertItemByAfterId(id: string, items: EncounterItem[], newItem: EncounterItem) {
|
||||
const index = getIndexById(id, items)
|
||||
if (index > -1) {
|
||||
items.splice(index + 1, 0, newItem)
|
||||
}
|
||||
}
|
||||
|
||||
export function injectComponents(id: string | number, data: EncounterListData, meta: EncounterListData) {
|
||||
const currentKeys = { ...defaultKeys }
|
||||
if (currentKeys?.status) {
|
||||
currentKeys.status['component'] = StatusAsync
|
||||
currentKeys.status['props'] = { encounter: data?.encounter }
|
||||
}
|
||||
if (currentKeys?.earlyMedicalAssessment) {
|
||||
currentKeys.earlyMedicalAssessment['component'] = EarlyMedicalAssesmentListAsync
|
||||
currentKeys.earlyMedicalAssessment['props'] = {
|
||||
encounter: data?.encounter,
|
||||
type: 'early-medic',
|
||||
label: currentKeys.earlyMedicalAssessment['title'],
|
||||
}
|
||||
}
|
||||
if (currentKeys?.earlyMedicalRehabAssessment) {
|
||||
currentKeys.earlyMedicalRehabAssessment['component'] = EarlyMedicalRehabListAsync
|
||||
currentKeys.earlyMedicalRehabAssessment['props'] = {
|
||||
encounter: data?.encounter,
|
||||
type: 'early-rehab',
|
||||
label: currentKeys.earlyMedicalRehabAssessment['title'],
|
||||
}
|
||||
}
|
||||
if (currentKeys?.functionAssessment) {
|
||||
currentKeys.functionAssessment['component'] = AssesmentFunctionListAsync
|
||||
currentKeys.functionAssessment['props'] = {
|
||||
encounter: data?.encounter,
|
||||
type: 'function',
|
||||
label: currentKeys.functionAssessment['title'],
|
||||
}
|
||||
}
|
||||
if (currentKeys?.therapyProtocol) {
|
||||
// TODO: add component for therapyProtocol
|
||||
currentKeys.therapyProtocol['component'] = null
|
||||
currentKeys.therapyProtocol['props'] = {
|
||||
data: data?.encounter,
|
||||
paginationMeta: meta?.protocolTheraphy,
|
||||
}
|
||||
}
|
||||
if (currentKeys?.chemotherapyProtocol) {
|
||||
currentKeys.chemotherapyProtocol['component'] = ChemoProtocolListAsync
|
||||
currentKeys.chemotherapyProtocol['props'] = {
|
||||
data: data?.encounter,
|
||||
paginationMeta: meta?.protocolChemotherapy,
|
||||
}
|
||||
}
|
||||
if (currentKeys?.chemotherapyMedicine) {
|
||||
currentKeys.chemotherapyMedicine['component'] = ChemoMedicineProtocolListAsync
|
||||
currentKeys.chemotherapyMedicine['props'] = {
|
||||
data: data?.encounter,
|
||||
paginationMeta: meta?.medicineProtocolChemotherapy,
|
||||
}
|
||||
}
|
||||
if (currentKeys?.educationAssessment) {
|
||||
// TODO: add component for education assessment
|
||||
currentKeys.educationAssessment['component'] = null
|
||||
currentKeys.educationAssessment['props'] = { encounter_id: id }
|
||||
}
|
||||
if (currentKeys?.generalConsent) {
|
||||
currentKeys.generalConsent['component'] = GeneralConsentListAsync
|
||||
currentKeys.generalConsent['props'] = { encounter_id: id }
|
||||
}
|
||||
if (currentKeys?.patientAmbNote) {
|
||||
currentKeys.patientAmbNote['component'] = CprjAsync
|
||||
currentKeys.patientAmbNote['props'] = { encounter_id: id }
|
||||
}
|
||||
if (currentKeys?.prescription) {
|
||||
currentKeys.prescription['component'] = PrescriptionAsync
|
||||
currentKeys.prescription['props'] = { encounter_id: id }
|
||||
}
|
||||
if (currentKeys?.deviceOrder) {
|
||||
currentKeys.deviceOrder['component'] = DeviceOrderAsync
|
||||
currentKeys.deviceOrder['props'] = { encounter_id: id }
|
||||
}
|
||||
if (currentKeys?.radiologyOrder) {
|
||||
currentKeys.radiologyOrder['component'] = RadiologyOrderAsync
|
||||
currentKeys.radiologyOrder['props'] = { encounter_id: id }
|
||||
}
|
||||
if (currentKeys?.cpLabOrder) {
|
||||
currentKeys.cpLabOrder['component'] = CpLabOrderAsync
|
||||
currentKeys.cpLabOrder['props'] = { encounter_id: id }
|
||||
}
|
||||
if (currentKeys?.microLabOrder) {
|
||||
currentKeys.microLabOrder['component'] = MicroLabOrderAsync
|
||||
currentKeys.microLabOrder['props'] = { encounter_id: id }
|
||||
}
|
||||
if (currentKeys?.paLabOrder) {
|
||||
// TODO: add component for paLabOrder
|
||||
currentKeys.paLabOrder['component'] = null
|
||||
currentKeys.paLabOrder['props'] = { encounter_id: id }
|
||||
}
|
||||
if (currentKeys?.procedureRoomOrder) {
|
||||
currentKeys.procedureRoomOrder['component'] = ProcedureRoomOrderAsync
|
||||
currentKeys.procedureRoomOrder['props'] = { encounter_id: id }
|
||||
}
|
||||
if (currentKeys?.mcuResult) {
|
||||
// TODO: add component for mcuResult
|
||||
currentKeys.mcuResult['component'] = null
|
||||
currentKeys.mcuResult['props'] = { encounter_id: id }
|
||||
}
|
||||
if (currentKeys?.consultation) {
|
||||
currentKeys.consultation['component'] = ConsultationAsync
|
||||
currentKeys.consultation['props'] = { encounter: data?.encounter }
|
||||
}
|
||||
if (currentKeys?.resume) {
|
||||
currentKeys.resume['component'] = ResumeListAsync
|
||||
currentKeys.resume['props'] = { encounter_id: id }
|
||||
}
|
||||
if (currentKeys?.controlLetter) {
|
||||
currentKeys.controlLetter['component'] = ControlLetterListAsync
|
||||
currentKeys.controlLetter['props'] = { encounter: data?.encounter }
|
||||
}
|
||||
if (currentKeys?.refBack) {
|
||||
currentKeys.refBack['component'] = PrbListAsync
|
||||
currentKeys.refBack['props'] = { encounter: data?.encounter }
|
||||
}
|
||||
if (currentKeys?.fkr) {
|
||||
currentKeys.fkr['component'] = KfrListAsync
|
||||
currentKeys.fkr['props'] = { encounter: data?.encounter }
|
||||
}
|
||||
if (currentKeys?.screening) {
|
||||
// TODO: add component for screening
|
||||
currentKeys.screening['component'] = null
|
||||
currentKeys.screening['props'] = { encounter_id: id }
|
||||
}
|
||||
if (currentKeys?.surgeryReport) {
|
||||
currentKeys.surgeryReport['component'] = SurgeryReportListAsync
|
||||
currentKeys.surgeryReport['props'] = { encounter: data?.encounter }
|
||||
}
|
||||
if (currentKeys?.vaccineData) {
|
||||
currentKeys.vaccineData['component'] = VaccineDataListAsync
|
||||
currentKeys.vaccineData['props'] = { encounter: data?.encounter }
|
||||
}
|
||||
if (currentKeys?.supportingDocument) {
|
||||
currentKeys.supportingDocument['component'] = DocUploadListAsync
|
||||
currentKeys.supportingDocument['props'] = { encounter_id: id }
|
||||
}
|
||||
if (currentKeys?.priceList) {
|
||||
// TODO: add component for priceList
|
||||
currentKeys.priceList['component'] = null
|
||||
currentKeys.priceList['props'] = { encounter_id: id }
|
||||
}
|
||||
|
||||
if (currentKeys?.initialNursingStudy) {
|
||||
currentKeys.initialNursingStudy['component'] = InitialNursingStudyAsync
|
||||
currentKeys.initialNursingStudy['props'] = { encounter: data?.encounter }
|
||||
}
|
||||
|
||||
return currentKeys
|
||||
}
|
||||
|
||||
export function mergeArrayAt<T>(arraysOne: T[], arraysTwo: T[] | T, deleteCount = 0): T[] {
|
||||
const prevItems = arraysOne.slice()
|
||||
if (!prevItems) return prevItems
|
||||
const nextItems = Array.isArray(arraysTwo) ? arraysTwo : [arraysTwo]
|
||||
if (nextItems.length === 0) return prevItems
|
||||
// determine insertion position using the first item's `id` if available
|
||||
const firstId = (nextItems[0] as any)?.afterId || (prevItems[0] as any)?.id
|
||||
let pos = prevItems.length
|
||||
if (typeof firstId === 'string') {
|
||||
const index = prevItems.findIndex((item: any) => item.id === firstId)
|
||||
pos = index < 0 ? Math.max(prevItems.length + index, 0) : Math.min(index, prevItems.length)
|
||||
}
|
||||
prevItems.splice(pos, deleteCount, ...nextItems)
|
||||
return prevItems
|
||||
}
|
||||
|
||||
// Function to map API response to Encounter structure
|
||||
export function mapResponseToEncounter(result: any): any {
|
||||
if (!result) return null
|
||||
|
||||
// Check if patient and patient.person exist (minimal validation)
|
||||
if (!result.patient || !result.patient.person) {
|
||||
return null
|
||||
}
|
||||
|
||||
const mapped: any = {
|
||||
id: result.id || 0,
|
||||
patient_id: result.patient_id || result.patient?.id || 0,
|
||||
patient: {
|
||||
id: result.patient?.id || 0,
|
||||
number: result.patient?.number || '',
|
||||
person: {
|
||||
id: result.patient?.person?.id || 0,
|
||||
name: result.patient?.person?.name || '',
|
||||
birthDate: result.patient?.person?.birthDate || null,
|
||||
gender_code: result.patient?.person?.gender_code || '',
|
||||
residentIdentityNumber: result.patient?.person?.residentIdentityNumber || null,
|
||||
frontTitle: result.patient?.person?.frontTitle || '',
|
||||
endTitle: result.patient?.person?.endTitle || '',
|
||||
addresses: result.patient?.person?.addresses || [],
|
||||
},
|
||||
},
|
||||
registeredAt: result.registeredAt || result.patient?.registeredAt || null,
|
||||
class_code: result.class_code || '',
|
||||
unit_id: result.unit_id || 0,
|
||||
unit: result.unit || null,
|
||||
specialist_id: result.specialist_id || null,
|
||||
subspecialist_id: result.subspecialist_id || null,
|
||||
visitDate: isValidDate(result.visitDate)
|
||||
? result.visitDate
|
||||
: result.registeredAt || result.patient?.registeredAt || null,
|
||||
adm_employee_id: result.adm_employee_id || 0,
|
||||
adm_employee: result.adm_employee || null,
|
||||
responsible_nurse_code: result.responsible_nurse_code || null,
|
||||
responsible_nurse: result.responsible_nurse || null,
|
||||
appointment_doctor_code: result.appointment_doctor_code || null,
|
||||
appointment_doctor: result.appointment_doctor || null,
|
||||
responsible_doctor_code: result.responsible_doctor_id || null,
|
||||
responsible_doctor: result.responsible_doctor || null,
|
||||
refSource_name: result.refSource_name || null,
|
||||
appointment_id: result.appointment_id || null,
|
||||
earlyEducation: result.earlyEducation || null,
|
||||
medicalDischargeEducation: result.medicalDischargeEducation || '',
|
||||
admDischargeEducation: result.admDischargeEducation || null,
|
||||
discharge_method_code: result.discharge_method_code || null,
|
||||
discharge_reason: result.dischargeReason || result.discharge_reason || null,
|
||||
discharge_date: result.discharge_date || null,
|
||||
status_code: result.status_code || '',
|
||||
// Payment related fields
|
||||
paymentMethod_code:
|
||||
result.paymentMethod_code && result.paymentMethod_code.trim() !== '' ? result.paymentMethod_code : null,
|
||||
trx_number: result.trx_number || null,
|
||||
member_number: result.member_number || null,
|
||||
ref_number: result.ref_number || null,
|
||||
}
|
||||
|
||||
return mapped
|
||||
}
|
||||
|
||||
export function getMenuItems(
|
||||
id: string | number,
|
||||
props: any,
|
||||
user: any,
|
||||
data: EncounterListData,
|
||||
meta: any,
|
||||
) {
|
||||
// const normalClassCode = props.classCode === 'ambulatory' ? 'outpatient' : props.classCode
|
||||
const normalClassCode = props.classCode === 'ambulatory' ? 'ambulatory' : props.classCode
|
||||
const currentKeys = injectComponents(id, data, meta)
|
||||
const defaultItems: EncounterItem[] = Object.values(currentKeys)
|
||||
const listItemsForOutpatientRehab = mergeArrayAt(
|
||||
getItemsAll('ambulatory', 'all', defaultItems),
|
||||
getItemsAll('ambulatory', 'rehab', defaultItems),
|
||||
)
|
||||
const listItemsForOutpatientChemo = mergeArrayAt(
|
||||
getItemsAll('ambulatory', 'all', defaultItems),
|
||||
getItemsAll('ambulatory', 'chemo', defaultItems),
|
||||
)
|
||||
const listItems: Record<string, Record<string, Record<string, any>>> = {
|
||||
'installation|ambulatory': {
|
||||
'unit|rehab': {
|
||||
items: listItemsForOutpatientRehab,
|
||||
roles: medicalRoles,
|
||||
},
|
||||
'unit|chemo': {
|
||||
items: listItemsForOutpatientChemo,
|
||||
roles: medicalRoles,
|
||||
},
|
||||
all: getItemsAll('ambulatory', 'all', defaultItems),
|
||||
},
|
||||
'installation|emergency': {
|
||||
all: getItemsAll('emergency', 'all', defaultItems),
|
||||
},
|
||||
'installation|inpatient': {
|
||||
all: getItemsAll('inpatient', 'all', defaultItems),
|
||||
},
|
||||
}
|
||||
const currentListItems = listItems[`installation|${normalClassCode}`]
|
||||
if (!currentListItems) return []
|
||||
const unitCode = user?.unit_code ? `unit|${user.unit_code}` : 'all'
|
||||
const currentUnitItems: any = currentListItems[`${unitCode}`]
|
||||
if (!currentUnitItems) return []
|
||||
let menus = []
|
||||
if (currentUnitItems.roles && currentUnitItems.roles?.includes(user.activeRole)) {
|
||||
menus = [...currentUnitItems.items]
|
||||
} else {
|
||||
menus = unitCode !== 'all' && currentUnitItems?.items ? [...currentUnitItems.items] : [...currentUnitItems]
|
||||
}
|
||||
return menus
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Handlers
|
||||
import { genCrudHandler } from '~/handlers/_handler'
|
||||
|
||||
// Services
|
||||
import { create, update, remove } from '~/services/item-price.service'
|
||||
|
||||
export const {
|
||||
recId,
|
||||
recAction,
|
||||
recItem,
|
||||
isReadonly,
|
||||
isProcessing,
|
||||
isFormEntryDialogOpen,
|
||||
isRecordConfirmationOpen,
|
||||
onResetState,
|
||||
handleActionSave,
|
||||
handleActionEdit,
|
||||
handleActionRemove,
|
||||
handleCancelForm,
|
||||
} = genCrudHandler({
|
||||
create,
|
||||
update,
|
||||
remove,
|
||||
})
|
||||
@@ -0,0 +1,24 @@
|
||||
// Handlers
|
||||
import { genCrudHandler } from '~/handlers/_handler'
|
||||
|
||||
// Services
|
||||
import { create, update, remove } from '~/services/item.service'
|
||||
|
||||
export const {
|
||||
recId,
|
||||
recAction,
|
||||
recItem,
|
||||
isReadonly,
|
||||
isProcessing,
|
||||
isFormEntryDialogOpen,
|
||||
isRecordConfirmationOpen,
|
||||
onResetState,
|
||||
handleActionSave,
|
||||
handleActionEdit,
|
||||
handleActionRemove,
|
||||
handleCancelForm,
|
||||
} = genCrudHandler({
|
||||
create,
|
||||
update,
|
||||
remove,
|
||||
})
|
||||
@@ -3,11 +3,10 @@ import CardContent from '~/components/pub/ui/card/CardContent.vue'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
|
||||
const contentFrame = computed(() => route.meta.contentFrame)
|
||||
const contentPadding = computed(() => route.meta.contentPadding || 'p-4 2xl:p-5')
|
||||
const contentUseCard = computed(() => route.meta.contentUseCard === false ? false : true)
|
||||
console.log(route.meta.contentUseCard,contentUseCard)
|
||||
const contentUseCard = computed(() => (route.meta.contentUseCard === false ? false : true))
|
||||
console.log(route.meta.contentUseCard, contentUseCard)
|
||||
const contentFrameClass = computed(() => {
|
||||
switch (contentFrame.value) {
|
||||
case 'cf-container-2xl':
|
||||
@@ -33,7 +32,7 @@ const contentFrameClass = computed(() => {
|
||||
<LayoutAppSidebar />
|
||||
<SidebarInset>
|
||||
<LayoutHeader />
|
||||
<div :class="`w-full flex justify-center ${contentPadding} ${contentFrameClass}`">
|
||||
<div :class="`flex w-full justify-center ${contentPadding} ${contentFrameClass}`">
|
||||
<div v-if="contentFrame !== 'cf-no-frame'">
|
||||
<Card v-if="contentUseCard">
|
||||
<CardContent>
|
||||
|
||||
@@ -79,6 +79,7 @@ export const paymentTypes: Record<string, string> = {
|
||||
jkmm: 'JKMM (Jaminan Kesehatan Mandiri)',
|
||||
spm: 'SPM (Sistem Pembayaran Mandiri)',
|
||||
pks: 'PKS (Pembiayaan Kesehatan Sosial)',
|
||||
umum: 'Umum',
|
||||
}
|
||||
|
||||
export const sepRefTypeCodes: Record<string, string> = {
|
||||
|
||||
+5
-12
@@ -19,25 +19,18 @@ export function usePageChecker() {
|
||||
) {
|
||||
// Check if user has access to this page, need to use try - catch for proper handling
|
||||
const hasAccess = checkRole(roleAccess)
|
||||
if (!hasAccess) {
|
||||
navigateTo('/403')
|
||||
}
|
||||
if (!hasAccess) return false
|
||||
|
||||
// Define permission-based computed properties
|
||||
const canCreate = hasCreateAccess(roleAccess)
|
||||
const canRead = hasReadAccess(roleAccess)
|
||||
const canUpdate = hasUpdateAccess(roleAccess)
|
||||
const canDelete = hasDeleteAccess(roleAccess)
|
||||
|
||||
switch (type) {
|
||||
case 'create':
|
||||
return canCreate
|
||||
return hasCreateAccess(roleAccess)
|
||||
case 'read':
|
||||
return canRead
|
||||
return hasReadAccess(roleAccess)
|
||||
case 'update':
|
||||
return canUpdate
|
||||
return hasUpdateAccess(roleAccess)
|
||||
case 'delete':
|
||||
return canDelete
|
||||
return hasDeleteAccess(roleAccess)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<script setup lang="ts">
|
||||
import Error from '~/components/pub/my-ui/error/error.vue'
|
||||
import Content from '~/components/content/item-price/list.vue'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
definePageMeta({
|
||||
middleware: ['rbac'],
|
||||
roles: ['emp|reg', 'emp|nur', 'emp|doc', 'emp|miw', 'emp|thr', 'emp|nut', 'emp|pha', 'emp|lab'],
|
||||
title: 'Daftar Item Harga',
|
||||
contentFrame: 'cf-container-lg',
|
||||
})
|
||||
|
||||
useHead({
|
||||
title: () => route.meta.title as string,
|
||||
})
|
||||
|
||||
const canRead = true
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<template v-if="canRead">
|
||||
<Content />
|
||||
</template>
|
||||
<Error
|
||||
v-else
|
||||
:status-code="403"
|
||||
/>
|
||||
</template>
|
||||
@@ -0,0 +1,29 @@
|
||||
<script setup lang="ts">
|
||||
import Error from '~/components/pub/my-ui/error/error.vue'
|
||||
import Content from '~/components/content/item/list.vue'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
definePageMeta({
|
||||
middleware: ['rbac'],
|
||||
roles: ['emp|reg', 'emp|nur', 'emp|doc', 'emp|miw', 'emp|thr', 'emp|nut', 'emp|pha', 'emp|lab'],
|
||||
title: 'Daftar Item',
|
||||
contentFrame: 'cf-container-lg',
|
||||
})
|
||||
|
||||
useHead({
|
||||
title: () => route.meta.title as string,
|
||||
})
|
||||
|
||||
const canRead = true
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<template v-if="canRead">
|
||||
<Content />
|
||||
</template>
|
||||
<Error
|
||||
v-else
|
||||
:status-code="403"
|
||||
/>
|
||||
</template>
|
||||
@@ -1,3 +1,4 @@
|
||||
import { isValid } from "date-fns"
|
||||
import { z } from 'zod'
|
||||
|
||||
const ERROR_MESSAGES = {
|
||||
@@ -16,6 +17,7 @@ const ERROR_MESSAGES = {
|
||||
}
|
||||
|
||||
const ACCEPTED_UPLOAD_TYPES = ['image/jpeg', 'image/png', 'application/pdf']
|
||||
const isValidationSep = false
|
||||
|
||||
const IntegrationEncounterSchema = z
|
||||
.object({
|
||||
@@ -116,7 +118,7 @@ const IntegrationEncounterSchema = z
|
||||
.refine(
|
||||
(data) => {
|
||||
// If payment type is jkn and SEP type is selected, then SEP number is required
|
||||
if (data.paymentMethod_code === 'jkn' && data.sepType && data.sepType.trim() !== '') {
|
||||
if (isValidationSep && data.paymentMethod_code === 'jkn' && data.sepType && data.sepType.trim() !== '') {
|
||||
return data.sepNumber && data.sepNumber.trim() !== ''
|
||||
}
|
||||
return true
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
const ItemPriceSchema = z.object({
|
||||
item_code: z.string({ required_error: 'Item harus diisi' }).min(1, 'Item harus diisi'),
|
||||
price: z.number({ required_error: 'Harga harus diisi' }).min(0, 'Harga tidak boleh kurang dari 0'),
|
||||
insuranceCompany_code: z.string({ required_error: 'Perusahaan Asuransi harus diisi' }).min(1, 'Perusahaan Asuransi harus diisi'),
|
||||
})
|
||||
|
||||
type ItemPriceFormData = z.infer<typeof ItemPriceSchema>
|
||||
|
||||
export { ItemPriceSchema }
|
||||
export type { ItemPriceFormData }
|
||||
@@ -0,0 +1,17 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
const ItemSchema = z.object({
|
||||
code: z.string({ required_error: 'Kode harus diisi' }).min(1, 'Kode minimum 1 karakter'),
|
||||
name: z.string({ required_error: 'Nama harus diisi' }).min(1, 'Nama minimum 1 karakter'),
|
||||
itemGroup_code: z.string({ required_error: 'Item Group harus diisi' }).min(1, 'Item Group harus diisi'),
|
||||
uom_code: z.string({ required_error: 'UOM harus diisi' }).min(1, 'UOM harus diisi'),
|
||||
infra_code: z.string({ required_error: 'Infra harus diisi' }).optional(),
|
||||
stock: z.number({ required_error: 'Stok harus diisi' }).min(0, 'Stok tidak boleh kurang dari 0').optional(),
|
||||
buyingPrice: z.number({ required_error: 'Harga Beli harus diisi' }).min(0, 'Harga Beli tidak boleh kurang dari 0').optional(),
|
||||
sellingPrice: z.number({ required_error: 'Harga Jual harus diisi' }).min(0, 'Harga Jual tidak boleh kurang dari 0').optional(),
|
||||
})
|
||||
|
||||
type ItemFormData = z.infer<typeof ItemSchema>
|
||||
|
||||
export { ItemSchema }
|
||||
export type { ItemFormData }
|
||||
@@ -0,0 +1,41 @@
|
||||
// Base
|
||||
import * as base from './_crud-base'
|
||||
|
||||
const path = '/api/v1/item-group'
|
||||
const name = 'item-group'
|
||||
|
||||
export function create(data: any) {
|
||||
return base.create(path, data, name)
|
||||
}
|
||||
|
||||
export function getList(params: any = null) {
|
||||
return base.getList(path, params, name)
|
||||
}
|
||||
|
||||
export function getDetail(id: number | string) {
|
||||
return base.getDetail(path, id, name)
|
||||
}
|
||||
|
||||
export function update(id: number | string, data: any) {
|
||||
return base.update(path, id, data, name)
|
||||
}
|
||||
|
||||
export function remove(id: number | string) {
|
||||
return base.remove(path, id, name)
|
||||
}
|
||||
|
||||
export async function getValueLabelList(
|
||||
params: any = null,
|
||||
useCodeAsValue = false,
|
||||
): Promise<{ value: string; label: string }[]> {
|
||||
let data: { value: string; label: string }[] = []
|
||||
const result = await getList(params)
|
||||
if (result.success) {
|
||||
const resultData = result.body?.data || []
|
||||
data = resultData.map((item: any) => ({
|
||||
value: useCodeAsValue ? item.code : item.id ? Number(item.id) : item.code,
|
||||
label: item.name,
|
||||
}))
|
||||
}
|
||||
return data
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// Base
|
||||
import * as base from './_crud-base'
|
||||
|
||||
const path = '/api/v1/item-price'
|
||||
const name = 'item-price'
|
||||
|
||||
export function create(data: any) {
|
||||
return base.create(path, data, name)
|
||||
}
|
||||
|
||||
export function getList(params: any = null) {
|
||||
return base.getList(path, params, name)
|
||||
}
|
||||
|
||||
export function getDetail(id: number | string) {
|
||||
return base.getDetail(path, id, name)
|
||||
}
|
||||
|
||||
export function update(id: number | string, data: any) {
|
||||
return base.update(path, id, data, name)
|
||||
}
|
||||
|
||||
export function remove(id: number | string) {
|
||||
return base.remove(path, id, name)
|
||||
}
|
||||
@@ -24,13 +24,16 @@ export function remove(id: number | string) {
|
||||
return base.remove(path, id, name)
|
||||
}
|
||||
|
||||
export async function getValueLabelList(params: any = null): Promise<{ value: string; label: string }[]> {
|
||||
export async function getValueLabelList(
|
||||
params: any = null,
|
||||
useCodeAsValue = false,
|
||||
): Promise<{ value: string; label: string }[]> {
|
||||
let data: { value: string; label: string }[] = []
|
||||
const result = await getList(params)
|
||||
if (result.success) {
|
||||
const resultData = result.body?.data || []
|
||||
data = resultData.map((item: any) => ({
|
||||
value: item.id ? Number(item.id) : item.code,
|
||||
value: useCodeAsValue ? item.code : item.id ? Number(item.id) : item.code,
|
||||
label: item.name,
|
||||
}))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user