Merge pull request #219 from dikstub-rssa/feat/adj-enc-list-199
Feat - Item + Item Price
This commit is contained in:
No files matched your search
@@ -1,4 +1,5 @@
|
|||||||
NUXT_MAIN_API_ORIGIN=
|
NUXT_MAIN_API_ORIGIN=
|
||||||
NUXT_BPJS_API_ORIGIN=
|
NUXT_BPJS_API_ORIGIN=
|
||||||
|
NUXT_API_VCLAIM_SWAGGER= # https://vclaim-api.multy.chat
|
||||||
NUXT_SYNC_API_ORIGIN=
|
NUXT_SYNC_API_ORIGIN=
|
||||||
NUXT_API_ORIGIN=
|
NUXT_API_ORIGIN=
|
||||||
@@ -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
|
// App things
|
||||||
import { genEncounter, type Encounter } from '~/models/encounter'
|
import { genEncounter, type Encounter } from '~/models/encounter'
|
||||||
import { se } from 'date-fns/locale'
|
|
||||||
|
|
||||||
// Props
|
// Props
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@@ -50,7 +49,6 @@ model.value = genEncounter()
|
|||||||
|
|
||||||
// Common preparation
|
// Common preparation
|
||||||
const defaultCBItems = [{ label: 'Pilih', value: '' }]
|
const defaultCBItems = [{ label: 'Pilih', value: '' }]
|
||||||
const paymentMethodItems = CB.recStrToItem(paymentMethodCodes)
|
|
||||||
|
|
||||||
// Emit preparation
|
// Emit preparation
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@@ -85,12 +83,10 @@ const sepTrafficStatus = ref('')
|
|||||||
const diagnosis = ref('')
|
const diagnosis = ref('')
|
||||||
const noteReference = ref('Hanya diperlukan jika pembayaran jenis JKN')
|
const noteReference = ref('Hanya diperlukan jika pembayaran jenis JKN')
|
||||||
const noteFile = ref('Gunakan file [.pdf, .jpg, .png] dengan ukuran maksimal 1MB')
|
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 isLoading = props.isLoading !== undefined ? props.isLoading : false
|
||||||
const isReadonly = props.isReadonly !== undefined ? props.isReadonly : false
|
const isReadonly = props.isReadonly !== undefined ? props.isReadonly : false
|
||||||
const mode = props.mode !== undefined ? props.mode : 'add'
|
const isSepValid = computed(() => props.isSepValid || false) // SEP validation state from props
|
||||||
// SEP validation state from props
|
|
||||||
const isSepValid = computed(() => props.isSepValid || false)
|
|
||||||
const isCheckingSep = computed(() => props.isCheckingSep || false)
|
const isCheckingSep = computed(() => props.isCheckingSep || false)
|
||||||
const isInsurancePayment = computed(() => ['insurance', 'jkn'].includes(paymentMethodCode.value))
|
const isInsurancePayment = computed(() => ['insurance', 'jkn'].includes(paymentMethodCode.value))
|
||||||
const isDateLoading = ref(false)
|
const isDateLoading = ref(false)
|
||||||
@@ -100,6 +96,7 @@ const sepFileReview = ref<any>(null)
|
|||||||
const sippFileReview = ref<any>(null)
|
const sippFileReview = ref<any>(null)
|
||||||
const unitFullName = ref('') // Unit, specialist, subspecialist
|
const unitFullName = ref('') // Unit, specialist, subspecialist
|
||||||
const formRef = ref<HTMLFormElement | null>(null) // Expose submit method for parent component
|
const formRef = ref<HTMLFormElement | null>(null) // Expose submit method for parent component
|
||||||
|
const paymentMethodItems = CB.recStrToItem(paymentMethodCodes)
|
||||||
|
|
||||||
if (mode === 'add') {
|
if (mode === 'add') {
|
||||||
// Set default sepDate to current date in YYYY-MM-DD format
|
// Set default sepDate to current date in YYYY-MM-DD format
|
||||||
@@ -129,13 +126,15 @@ watch(
|
|||||||
nationalIdentity.value = objects?.nationalIdentity || ''
|
nationalIdentity.value = objects?.nationalIdentity || ''
|
||||||
medicalRecordNumber.value = objects?.medicalRecordNumber || ''
|
medicalRecordNumber.value = objects?.medicalRecordNumber || ''
|
||||||
doctorCode.value = objects?.doctorCode || ''
|
doctorCode.value = objects?.doctorCode || ''
|
||||||
paymentMethodCode.value = objects?.paymentMethodCode || ''
|
|
||||||
patientCategory.value = objects?.patientCategory || ''
|
patientCategory.value = objects?.patientCategory || ''
|
||||||
cardNumber.value = objects?.cardNumber || ''
|
cardNumber.value = objects?.cardNumber || ''
|
||||||
sepType.value = objects?.sepType || ''
|
sepType.value = objects?.sepType || ''
|
||||||
sepNumber.value = objects?.sepNumber || ''
|
sepNumber.value = objects?.sepNumber || ''
|
||||||
sepFileReview.value = objects?.sepFileReview || ''
|
sepFileReview.value = objects?.sepFileReview || ''
|
||||||
sippFileReview.value = objects?.sippFileReview || ''
|
sippFileReview.value = objects?.sippFileReview || ''
|
||||||
|
if (objects.paymentType) {
|
||||||
|
paymentMethodCode.value = objects.paymentType || ''
|
||||||
|
}
|
||||||
isDateLoading.value = true
|
isDateLoading.value = true
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
registerDate.value = objects?.registerDate || ''
|
registerDate.value = objects?.registerDate || ''
|
||||||
@@ -348,7 +347,7 @@ defineExpose({
|
|||||||
placeholder="Pilih Dokter"
|
placeholder="Pilih Dokter"
|
||||||
search-placeholder="Cari Dokter"
|
search-placeholder="Cari Dokter"
|
||||||
empty-message="Dokter tidak ditemukan"
|
empty-message="Dokter tidak ditemukan"
|
||||||
@update:model-value="(value) => emit('onSelectDoctor', value)"
|
@update:model-value="(value: any) => emit('onSelectDoctor', value)"
|
||||||
/>
|
/>
|
||||||
</DE.Field>
|
</DE.Field>
|
||||||
</DE.Cell>
|
</DE.Cell>
|
||||||
@@ -395,12 +394,12 @@ defineExpose({
|
|||||||
<span class="text-red-500">*</span>
|
<span class="text-red-500">*</span>
|
||||||
</DE.Label>
|
</DE.Label>
|
||||||
<DE.Field :errMessage="errors.paymentMethod_code">
|
<DE.Field :errMessage="errors.paymentMethod_code">
|
||||||
<CB.Combobox
|
<Select
|
||||||
id="paymentMethodCode"
|
id="paymentMethodCode"
|
||||||
v-model="paymentMethodCode"
|
v-model="paymentMethodCode"
|
||||||
v-bind="paymentMethodCodeAttrs"
|
v-bind="paymentMethodCodeAttrs"
|
||||||
:items="paymentMethodItems"
|
:items="payments || []"
|
||||||
:disabled="isLoading || isReadonly"
|
:disabled="isLoading || isReadonly || mode === 'edit'"
|
||||||
placeholder="Pilih Jenis Pembayaran"
|
placeholder="Pilih Jenis Pembayaran"
|
||||||
/>
|
/>
|
||||||
</DE.Field>
|
</DE.Field>
|
||||||
@@ -576,7 +575,7 @@ defineExpose({
|
|||||||
</span>
|
</span>
|
||||||
<p v-if="sepFileReview">
|
<p v-if="sepFileReview">
|
||||||
<a
|
<a
|
||||||
class="mt-1 text-sm text-blue-500 capitalize"
|
class="mt-1 text-sm capitalize text-blue-500"
|
||||||
href="#"
|
href="#"
|
||||||
@click="openFile(sepFileReview.filePath)"
|
@click="openFile(sepFileReview.filePath)"
|
||||||
>
|
>
|
||||||
@@ -601,7 +600,7 @@ defineExpose({
|
|||||||
</span>
|
</span>
|
||||||
<p v-if="sippFileReview">
|
<p v-if="sippFileReview">
|
||||||
<a
|
<a
|
||||||
class="mt-1 text-sm text-blue-500 capitalize"
|
class="mt-1 text-sm capitalize text-blue-500"
|
||||||
href="#"
|
href="#"
|
||||||
@click="openFile(sippFileReview.filePath)"
|
@click="openFile(sippFileReview.filePath)"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,50 +1,140 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Block from '~/components/pub/my-ui/form/block.vue'
|
// Components
|
||||||
import FieldGroup from '~/components/pub/my-ui/form/field-group.vue'
|
import Block from '~/components/pub/my-ui/doc-entry/block.vue'
|
||||||
import Field from '~/components/pub/my-ui/form/field.vue'
|
import Cell from '~/components/pub/my-ui/doc-entry/cell.vue'
|
||||||
import Label from '~/components/pub/my-ui/form/label.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 }>()
|
// Types
|
||||||
const emit = defineEmits(['update:modelValue', 'event'])
|
import type { ItemPriceFormData } from '~/schemas/item-price.schema'
|
||||||
|
|
||||||
const data = computed({
|
// Helpers
|
||||||
get: () => props.modelValue,
|
import type z from 'zod'
|
||||||
set: (val) => emit('update:modelValue', val),
|
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 = [
|
const [item_code, item_codeAttrs] = defineField('item_code')
|
||||||
{ value: '1', label: 'item 1' },
|
const [price, priceAttrs] = defineField('price')
|
||||||
{ value: '2', label: 'item 2' },
|
const [insuranceCompany_code, insuranceCompany_codeAttrs] = defineField('insuranceCompany_code')
|
||||||
{ value: '3', label: 'item 3' },
|
|
||||||
{ value: '4', label: 'item 4' },
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<form id="entry-form">
|
<form
|
||||||
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg xl:text-xl">
|
id="form-item-price"
|
||||||
<div class="flex flex-col justify-between">
|
@submit.prevent
|
||||||
<Block>
|
>
|
||||||
<FieldGroup>
|
<Block
|
||||||
<Label>Items</Label>
|
labelSize="thin"
|
||||||
<Field>
|
class="!mb-2.5 !pt-0 xl:!mb-3"
|
||||||
<Select :items="items" />
|
:colCount="1"
|
||||||
</Field>
|
>
|
||||||
</FieldGroup>
|
<Cell>
|
||||||
<FieldGroup>
|
<Label height="compact">Item</Label>
|
||||||
<Label>Perusahaan Insuransi</Label>
|
<Field :errMessage="errors.item_code">
|
||||||
<Field>
|
<Select
|
||||||
<Select :items="items" />
|
id="item_code"
|
||||||
</Field>
|
v-model="item_code"
|
||||||
</FieldGroup>
|
icon-name="i-lucide-chevron-down"
|
||||||
<FieldGroup>
|
placeholder="Pilih Item"
|
||||||
<Label>Harga</Label>
|
v-bind="item_codeAttrs"
|
||||||
<Field>
|
:items="items"
|
||||||
<Input v-model="data.price" />
|
:disabled="isLoading || isReadonly"
|
||||||
</Field>
|
/>
|
||||||
</FieldGroup>
|
</Field>
|
||||||
</Block>
|
</Cell>
|
||||||
</div>
|
<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>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
@@ -3,27 +3,23 @@ import { defineAsyncComponent } from 'vue'
|
|||||||
|
|
||||||
const action = defineAsyncComponent(() => import('~/components/pub/my-ui/data/dropdown-action-dud.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 = {
|
export const config: Config = {
|
||||||
cols: [{}, {}, { width: 50 }],
|
cols: [{}, {}, {}, { width: 50 }],
|
||||||
|
|
||||||
headers: [
|
headers: [
|
||||||
[
|
[
|
||||||
{ label: 'Kode' },
|
{ label: 'Item' },
|
||||||
{ label: 'Nama' },
|
{ label: 'Harga' },
|
||||||
|
{ label: 'Perusahaan Asuransi' },
|
||||||
{ label: 'Aksi' },
|
{ label: 'Aksi' },
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
keys: ['code', 'name', 'action'],
|
keys: ['item_code', 'price', 'insuranceCompany_code', 'action'],
|
||||||
|
|
||||||
delKeyNames: [
|
delKeyNames: [
|
||||||
{ key: 'code', label: 'Kode' },
|
{ key: 'item_code', label: 'Item' },
|
||||||
{ key: 'name', label: 'Nama' },
|
{ key: 'insuranceCompany_code', label: 'Perusahaan Asuransi' },
|
||||||
],
|
],
|
||||||
|
|
||||||
parses: {},
|
parses: {},
|
||||||
@@ -39,9 +35,5 @@ export const config: Config = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
htmls: {
|
htmls: {},
|
||||||
patient_address(_rec) {
|
|
||||||
return '-'
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
@@ -1,14 +1,39 @@
|
|||||||
<script setup lang="ts">
|
<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'
|
import { config } from './list-cfg'
|
||||||
|
|
||||||
defineProps<{
|
interface Props {
|
||||||
data: any[]
|
data: any[]
|
||||||
|
paginationMeta: PaginationMeta
|
||||||
|
}
|
||||||
|
|
||||||
|
defineProps<Props>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
pageChange: [page: number]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
function handlePageChange(page: number) {
|
||||||
|
emit('pageChange', page)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<PubMyUiDataTable
|
<div class="space-y-4">
|
||||||
v-bind="config"
|
<PubMyUiDataTable
|
||||||
:rows="data"
|
v-bind="config"
|
||||||
/>
|
:rows="data"
|
||||||
|
:skeleton-size="paginationMeta?.pageSize"
|
||||||
|
/>
|
||||||
|
<PaginationView
|
||||||
|
:pagination-meta="paginationMeta"
|
||||||
|
@page-change="handlePageChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</template>
|
</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">
|
<script setup lang="ts">
|
||||||
import Block from '~/components/pub/my-ui/form/block.vue'
|
// Components
|
||||||
import FieldGroup from '~/components/pub/my-ui/form/field-group.vue'
|
import Block from '~/components/pub/my-ui/doc-entry/block.vue'
|
||||||
import Field from '~/components/pub/my-ui/form/field.vue'
|
import Cell from '~/components/pub/my-ui/doc-entry/cell.vue'
|
||||||
import Label from '~/components/pub/my-ui/form/label.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 }>()
|
// Types
|
||||||
const emit = defineEmits(['update:modelValue', 'event'])
|
import type { ItemFormData } from '~/schemas/item.schema'
|
||||||
|
|
||||||
const data = computed({
|
// Helpers
|
||||||
get: () => props.modelValue,
|
import type z from 'zod'
|
||||||
set: (val) => emit('update:modelValue', val),
|
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 = [
|
const [code, codeAttrs] = defineField('code')
|
||||||
{ value: '1', label: 'item 1' },
|
const [name, nameAttrs] = defineField('name')
|
||||||
{ value: '2', label: 'item 2' },
|
const [itemGroup_code, itemGroup_codeAttrs] = defineField('itemGroup_code')
|
||||||
{ value: '3', label: 'item 3' },
|
const [uom, uomAttrs] = defineField('uom_code')
|
||||||
{ value: '4', label: 'item 4' },
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<form id="entry-form">
|
<form
|
||||||
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg xl:text-xl">
|
id="form-item"
|
||||||
<div class="flex flex-col justify-between">
|
@submit.prevent
|
||||||
<Block>
|
>
|
||||||
<FieldGroup>
|
<Block
|
||||||
<Label>Nama</Label>
|
labelSize="thin"
|
||||||
<Field>
|
class="!mb-2.5 !pt-0 xl:!mb-3"
|
||||||
<Input v-model="data.name" />
|
:colCount="1"
|
||||||
</Field>
|
>
|
||||||
</FieldGroup>
|
<Cell>
|
||||||
<FieldGroup>
|
<Label height="compact">Kode</Label>
|
||||||
<Label>Kode</Label>
|
<Field :errMessage="errors.code">
|
||||||
<Field>
|
<Input
|
||||||
<Input v-model="data.code" />
|
id="code"
|
||||||
</Field>
|
v-model="code"
|
||||||
</FieldGroup>
|
v-bind="codeAttrs"
|
||||||
<FieldGroup>
|
:disabled="isLoading || isReadonly"
|
||||||
<Label>Item Group</Label>
|
/>
|
||||||
<Field>
|
</Field>
|
||||||
<Select :items="items" />
|
</Cell>
|
||||||
</Field>
|
<Cell>
|
||||||
</FieldGroup>
|
<Label height="compact">Nama</Label>
|
||||||
<FieldGroup>
|
<Field :errMessage="errors.name">
|
||||||
<Label>UOM</Label>
|
<Input
|
||||||
<Field>
|
id="name"
|
||||||
<Select :items="items" />
|
v-model="name"
|
||||||
</Field>
|
v-bind="nameAttrs"
|
||||||
</FieldGroup>
|
:disabled="isLoading || isReadonly"
|
||||||
<FieldGroup>
|
/>
|
||||||
<Label>Infra</Label>
|
</Field>
|
||||||
<Field>
|
</Cell>
|
||||||
<Select :items="items" />
|
<Cell>
|
||||||
</Field>
|
<Label height="compact">Item Group</Label>
|
||||||
</FieldGroup>
|
<Field :errMessage="errors.itemGroup_code">
|
||||||
<FieldGroup>
|
<Select
|
||||||
<Label>Harga</Label>
|
id="itemGroup_code"
|
||||||
<Field>
|
v-model="itemGroup_code"
|
||||||
<Input v-model="data.price" />
|
icon-name="i-lucide-chevron-down"
|
||||||
</Field>
|
placeholder="Pilih Item Group"
|
||||||
</FieldGroup>
|
v-bind="itemGroup_codeAttrs"
|
||||||
</Block>
|
:items="itemGroups"
|
||||||
</div>
|
: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>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
@@ -3,30 +3,78 @@ import { defineAsyncComponent } from 'vue'
|
|||||||
|
|
||||||
const action = defineAsyncComponent(() => import('~/components/pub/my-ui/data/dropdown-action-dud.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 = {
|
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: [
|
headers: [
|
||||||
[
|
[
|
||||||
{ label: 'Kode' },
|
{ label: 'Kode' },
|
||||||
{ label: 'Nama' },
|
{ label: 'Nama' },
|
||||||
|
{ label: 'Item Group' },
|
||||||
|
{ label: 'UOM' },
|
||||||
|
{ label: 'Infra' },
|
||||||
|
{ label: 'Stok' },
|
||||||
|
{ label: 'Harga Beli' },
|
||||||
|
{ label: 'Harga Jual' },
|
||||||
{ label: 'Aksi' },
|
{ label: 'Aksi' },
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
keys: ['code', 'name', 'action'],
|
keys: ['code', 'name', 'itemGroup_code', 'uom_code', 'infra_code', 'stock', 'buyingPrice', 'sellingPrice', 'action'],
|
||||||
|
|
||||||
delKeyNames: [
|
delKeyNames: [
|
||||||
{ key: 'code', label: 'Kode' },
|
{ key: 'code', label: 'Kode' },
|
||||||
{ key: 'name', label: 'Nama' },
|
{ 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: {
|
components: {
|
||||||
action(rec, idx) {
|
action(rec, idx) {
|
||||||
@@ -39,9 +87,5 @@ export const config: Config = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
htmls: {
|
htmls: {},
|
||||||
patient_address(_rec) {
|
|
||||||
return '-'
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
@@ -1,14 +1,39 @@
|
|||||||
<script setup lang="ts">
|
<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'
|
import { config } from './list-cfg'
|
||||||
|
|
||||||
defineProps<{
|
interface Props {
|
||||||
data: any[]
|
data: any[]
|
||||||
|
paginationMeta: PaginationMeta
|
||||||
|
}
|
||||||
|
|
||||||
|
defineProps<Props>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
pageChange: [page: number]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
function handlePageChange(page: number) {
|
||||||
|
emit('pageChange', page)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<PubMyUiDataTable
|
<div class="space-y-4">
|
||||||
v-bind="config"
|
<PubMyUiDataTable
|
||||||
:rows="data"
|
v-bind="config"
|
||||||
/>
|
:rows="data"
|
||||||
|
:skeleton-size="paginationMeta?.pageSize"
|
||||||
|
/>
|
||||||
|
<PaginationView
|
||||||
|
:pagination-meta="paginationMeta"
|
||||||
|
@page-change="handlePageChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</template>
|
</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>
|
|
||||||
@@ -188,6 +188,7 @@ onMounted(async () => {
|
|||||||
@event="handleEvent"
|
@event="handleEvent"
|
||||||
@fetch="handleFetch"
|
@fetch="handleFetch"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<AppViewPatient
|
<AppViewPatient
|
||||||
v-model:open="openPatient"
|
v-model:open="openPatient"
|
||||||
v-model:selected="selectedPatient"
|
v-model:selected="selectedPatient"
|
||||||
@@ -206,11 +207,13 @@ onMounted(async () => {
|
|||||||
"
|
"
|
||||||
@save="handleSavePatient"
|
@save="handleSavePatient"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<AppViewHistory
|
<AppViewHistory
|
||||||
v-model:open="openHistory"
|
v-model:open="openHistory"
|
||||||
:is-action="true"
|
:is-action="true"
|
||||||
:histories="histories"
|
:histories="histories"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Footer Actions -->
|
<!-- Footer Actions -->
|
||||||
<div class="mt-6 flex justify-end gap-2 border-t border-t-slate-300 pt-4">
|
<div class="mt-6 flex justify-end gap-2 border-t border-t-slate-300 pt-4">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -1,70 +1,198 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { DataTableLoader } from '~/components/pub/my-ui/data-table/type'
|
// Components
|
||||||
import type { HeaderPrep, RefSearchNav } from '~/components/pub/my-ui/data/types'
|
import Dialog from '~/components/pub/my-ui/modal/dialog.vue'
|
||||||
import Modal from '~/components/pub/my-ui/modal/modal.vue'
|
|
||||||
import Header from '~/components/pub/my-ui/nav-header/prep.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([])
|
// Constants
|
||||||
const entry = ref<any>({})
|
import { infraGroupCodesKeys } from '~/lib/constants'
|
||||||
|
|
||||||
const refSearchNav: RefSearchNav = {
|
// Helpers
|
||||||
onClick: () => {
|
import { usePaginatedList } from '~/composables/usePaginatedList'
|
||||||
// open filter modal
|
import { toast } from '~/components/pub/ui/toast'
|
||||||
},
|
|
||||||
onInput: (_val: string) => {
|
|
||||||
// filter patient list
|
|
||||||
},
|
|
||||||
onClear: () => {
|
|
||||||
// clear url param
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loading state management
|
// Types
|
||||||
const isLoading = reactive<DataTableLoader>({
|
import { ActionEvents, type HeaderPrep } from '~/components/pub/my-ui/data/types'
|
||||||
summary: false,
|
import { ItemPriceSchema, type ItemPriceFormData } from '~/schemas/item-price.schema'
|
||||||
isTableLoading: false,
|
|
||||||
|
// 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 headerPrep: HeaderPrep = {
|
||||||
|
title: 'Harga Item',
|
||||||
const recId = ref<number>(0)
|
icon: 'i-lucide-layout-list',
|
||||||
const recAction = ref<string>('')
|
refSearchNav: {
|
||||||
const recItem = ref<any>(null)
|
placeholder: 'Cari (min. 3 karakter)...',
|
||||||
|
minLength: 3,
|
||||||
const hreaderPrep: HeaderPrep = {
|
debounceMs: 500,
|
||||||
title: 'Golongan Obat',
|
showValidationFeedback: true,
|
||||||
icon: 'i-lucide-users',
|
onInput: (val: string) => {
|
||||||
|
searchInput.value = val
|
||||||
|
},
|
||||||
|
onClick: () => {},
|
||||||
|
onClear: () => {},
|
||||||
|
},
|
||||||
addNav: {
|
addNav: {
|
||||||
label: 'Tambah',
|
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_id', recId)
|
||||||
provide('rec_action', recAction)
|
provide('rec_action', recAction)
|
||||||
provide('rec_item', recItem)
|
provide('rec_item', recItem)
|
||||||
provide('table_data_loader', isLoading)
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<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>
|
<Dialog
|
||||||
<AppMedicineGroupEntryForm v-model="entry" />
|
v-model:open="isFormEntryDialogOpen"
|
||||||
</Modal>
|
: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>
|
</template>
|
||||||
@@ -1,70 +1,208 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { DataTableLoader } from '~/components/pub/my-ui/data-table/type'
|
// Components
|
||||||
import type { HeaderPrep, RefSearchNav } from '~/components/pub/my-ui/data/types'
|
import Dialog from '~/components/pub/my-ui/modal/dialog.vue'
|
||||||
import Modal from '~/components/pub/my-ui/modal/modal.vue'
|
|
||||||
import Header from '~/components/pub/my-ui/nav-header/prep.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([])
|
// Constants
|
||||||
const entry = ref<any>({})
|
import { itemGroupCodes } from '~/lib/constants'
|
||||||
|
|
||||||
const refSearchNav: RefSearchNav = {
|
// Helpers
|
||||||
onClick: () => {
|
import { usePaginatedList } from '~/composables/usePaginatedList'
|
||||||
// open filter modal
|
import { toast } from '~/components/pub/ui/toast'
|
||||||
},
|
|
||||||
onInput: (_val: string) => {
|
|
||||||
// filter patient list
|
|
||||||
},
|
|
||||||
onClear: () => {
|
|
||||||
// clear url param
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loading state management
|
// Types
|
||||||
const isLoading = reactive<DataTableLoader>({
|
import { ActionEvents, type HeaderPrep } from '~/components/pub/my-ui/data/types'
|
||||||
summary: false,
|
import { ItemSchema, type ItemFormData } from '~/schemas/item.schema'
|
||||||
isTableLoading: false,
|
|
||||||
|
// 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 headerPrep: HeaderPrep = {
|
||||||
|
|
||||||
const recId = ref<number>(0)
|
|
||||||
const recAction = ref<string>('')
|
|
||||||
const recItem = ref<any>(null)
|
|
||||||
|
|
||||||
const hreaderPrep: HeaderPrep = {
|
|
||||||
title: 'Item',
|
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: {
|
addNav: {
|
||||||
label: 'Tambah',
|
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_id', recId)
|
||||||
provide('rec_action', recAction)
|
provide('rec_action', recAction)
|
||||||
provide('rec_item', recItem)
|
provide('rec_item', recItem)
|
||||||
provide('table_data_loader', isLoading)
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<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>
|
<Dialog
|
||||||
<AppItemEntryForm v-model="entry" />
|
v-model:open="isFormEntryDialogOpen"
|
||||||
</Modal>
|
: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>
|
</template>
|
||||||
@@ -75,6 +75,7 @@ export function useEncounterEntry(props: {
|
|||||||
const isSaveDisabled = computed(() => {
|
const isSaveDisabled = computed(() => {
|
||||||
return !selectedPatient.value || !selectedPatientObject.value || isSaving.value || isLoadingDetail.value
|
return !selectedPatient.value || !selectedPatientObject.value || isSaving.value || isLoadingDetail.value
|
||||||
})
|
})
|
||||||
|
const isUsePaymentNew = true
|
||||||
|
|
||||||
function getListPath(): string {
|
function getListPath(): string {
|
||||||
if (props.classCode === 'ambulatory') {
|
if (props.classCode === 'ambulatory') {
|
||||||
@@ -436,7 +437,7 @@ export function useEncounterEntry(props: {
|
|||||||
formData.registerDate = date.toISOString().split('T')[0]
|
formData.registerDate = date.toISOString().split('T')[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
if (encounter.paymentMethod_code) {
|
if (!isUsePaymentNew && encounter.paymentMethod_code) {
|
||||||
formData.paymentMethodCode = encounter.paymentMethod_code
|
formData.paymentMethodCode = encounter.paymentMethod_code
|
||||||
if (encounter.paymentMethod_code === 'insurance') {
|
if (encounter.paymentMethod_code === 'insurance') {
|
||||||
formData.paymentType = 'jkn'
|
formData.paymentType = 'jkn'
|
||||||
@@ -449,7 +450,7 @@ export function useEncounterEntry(props: {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
formData.paymentType = 'spm'
|
formData.paymentType = encounter.paymentMethod_code
|
||||||
}
|
}
|
||||||
|
|
||||||
formData.cardNumber = encounter.member_number || ''
|
formData.cardNumber = encounter.member_number || ''
|
||||||
@@ -520,7 +521,7 @@ export function useEncounterEntry(props: {
|
|||||||
sippFile.value = formValues.sippFile || null
|
sippFile.value = formValues.sippFile || null
|
||||||
|
|
||||||
let paymentMethodCode = formValues.paymentMethod_code ?? null
|
let paymentMethodCode = formValues.paymentMethod_code ?? null
|
||||||
if (!paymentMethodCode) {
|
if (!isUsePaymentNew && !paymentMethodCode) {
|
||||||
if (formValues.paymentType === 'jkn' || formValues.paymentType === 'jkmm') {
|
if (formValues.paymentType === 'jkn' || formValues.paymentType === 'jkmm') {
|
||||||
paymentMethodCode = 'insurance'
|
paymentMethodCode = 'insurance'
|
||||||
} else if (formValues.paymentType === 'spm') {
|
} else if (formValues.paymentType === 'spm') {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
})
|
||||||
@@ -79,6 +79,7 @@ export const paymentTypes: Record<string, string> = {
|
|||||||
jkmm: 'JKMM (Jaminan Kesehatan Mandiri)',
|
jkmm: 'JKMM (Jaminan Kesehatan Mandiri)',
|
||||||
spm: 'SPM (Sistem Pembayaran Mandiri)',
|
spm: 'SPM (Sistem Pembayaran Mandiri)',
|
||||||
pks: 'PKS (Pembiayaan Kesehatan Sosial)',
|
pks: 'PKS (Pembiayaan Kesehatan Sosial)',
|
||||||
|
umum: 'Umum',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const sepRefTypeCodes: Record<string, string> = {
|
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
|
// Check if user has access to this page, need to use try - catch for proper handling
|
||||||
const hasAccess = checkRole(roleAccess)
|
const hasAccess = checkRole(roleAccess)
|
||||||
if (!hasAccess) {
|
if (!hasAccess) return false
|
||||||
navigateTo('/403')
|
|
||||||
}
|
|
||||||
|
|
||||||
// Define permission-based computed properties
|
// Define permission-based computed properties
|
||||||
const canCreate = hasCreateAccess(roleAccess)
|
|
||||||
const canRead = hasReadAccess(roleAccess)
|
|
||||||
const canUpdate = hasUpdateAccess(roleAccess)
|
|
||||||
const canDelete = hasDeleteAccess(roleAccess)
|
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'create':
|
case 'create':
|
||||||
return canCreate
|
return hasCreateAccess(roleAccess)
|
||||||
case 'read':
|
case 'read':
|
||||||
return canRead
|
return hasReadAccess(roleAccess)
|
||||||
case 'update':
|
case 'update':
|
||||||
return canUpdate
|
return hasUpdateAccess(roleAccess)
|
||||||
case 'delete':
|
case 'delete':
|
||||||
return canDelete
|
return hasDeleteAccess(roleAccess)
|
||||||
default:
|
default:
|
||||||
return false
|
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'
|
import { z } from 'zod'
|
||||||
|
|
||||||
const ERROR_MESSAGES = {
|
const ERROR_MESSAGES = {
|
||||||
@@ -16,6 +17,7 @@ const ERROR_MESSAGES = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ACCEPTED_UPLOAD_TYPES = ['image/jpeg', 'image/png', 'application/pdf']
|
const ACCEPTED_UPLOAD_TYPES = ['image/jpeg', 'image/png', 'application/pdf']
|
||||||
|
const isValidationSep = false
|
||||||
|
|
||||||
const IntegrationEncounterSchema = z
|
const IntegrationEncounterSchema = z
|
||||||
.object({
|
.object({
|
||||||
@@ -116,7 +118,7 @@ const IntegrationEncounterSchema = z
|
|||||||
.refine(
|
.refine(
|
||||||
(data) => {
|
(data) => {
|
||||||
// If payment type is jkn and SEP type is selected, then SEP number is required
|
// If payment type is jkn and SEP type is selected, then SEP number is required
|
||||||
if (data.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 data.sepNumber && data.sepNumber.trim() !== ''
|
||||||
}
|
}
|
||||||
return true
|
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)
|
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 }[] = []
|
let data: { value: string; label: string }[] = []
|
||||||
const result = await getList(params)
|
const result = await getList(params)
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
const resultData = result.body?.data || []
|
const resultData = result.body?.data || []
|
||||||
data = resultData.map((item: any) => ({
|
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,
|
label: item.name,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user