feat(sep): add form support code

This commit is contained in:
riefive
2025-10-22 13:38:17 +07:00
parent d11b22dc05
commit baad005522
5 changed files with 263 additions and 110 deletions
+159 -55
View File
@@ -19,6 +19,7 @@ import type { PatientEntity } from '~/models/patient'
// Helpers
import { toTypedSchema } from '@vee-validate/zod'
import { useForm } from 'vee-validate'
import { se } from "date-fns/locale"
const props = defineProps<{
isLoading?: boolean
@@ -26,6 +27,12 @@ const props = defineProps<{
doctors: any[]
diagnoses: any[]
facilities: any[]
serviceTypes: any[]
registerMethods: any[]
accidents: any[]
purposes: any[]
procedures: any[]
assessments: any[]
supportCodes: any[]
patient?: PatientEntity | null | undefined
values?: any
@@ -39,11 +46,6 @@ const emit = defineEmits<{
const isLoading = props.isLoading !== undefined ? props.isLoading : false
const isReadonly = props.isReadonly !== undefined ? props.isReadonly : false
const admissionTypes = [
{ value: '1', label: 'Kontrol' },
{ value: '2', label: 'Rujukan' },
]
// Validation schema (moved to shared file)
const { handleSubmit, errors, defineField, meta } = useForm<IntegrationBpjsFormData>({
validationSchema: toTypedSchema(IntegrationBpjsSchema),
@@ -51,6 +53,7 @@ const { handleSubmit, errors, defineField, meta } = useForm<IntegrationBpjsFormD
// Bind fields and extract attrs for consistent Field pattern
const [sepDate, sepDateAttrs] = defineField('sepDate')
const [serviceType, serviceTypeAttrs] = defineField('serviceType')
const [admissionType, admissionTypeAttrs] = defineField('admissionType')
const [bpjsNumber, bpjsNumberAttrs] = defineField('bpjsNumber')
const [nationalId, nationalIdAttrs] = defineField('nationalId')
@@ -59,6 +62,7 @@ const [patientName, patientNameAttrs] = defineField('patientName')
const [phoneNumber, phoneNumberAttrs] = defineField('phoneNumber')
const [referralLetterNumber, referralLetterNumberAttrs] = defineField('referralLetterNumber')
const [referralLetterDate, referralLetterDateAttrs] = defineField('referralLetterDate')
const [fromClinic, fromClinicAttrs] = defineField('fromClinic')
const [destinationClinic, destinationClinicAttrs] = defineField('destinationClinic')
const [attendingDoctor, attendingDoctorAttrs] = defineField('attendingDoctor')
const [initialDiagnosis, initialDiagnosisAttrs] = defineField('initialDiagnosis')
@@ -68,6 +72,13 @@ const [clinicExcecutive, clinicExcecutiveAttrs] = defineField('clinicExcecutive'
const [procedureType, procedureTypeAttrs] = defineField('procedureType')
const [supportCode, supportCodeAttrs] = defineField('supportCode')
const [note, noteAttrs] = defineField('note')
const [accident, accidentAttrs] = defineField('trafficAccident')
const [purposeOfVisit, purposeOfVisitAttrs] = defineField('purposeOfVisit')
const [serviceAssessment, serviceAssessmentAttrs] = defineField('serviceAssessment')
const titleLetterNumber = computed(() => (admissionType.value === '3' ? 'Surat Kontrol' : 'Surat Rujukan'))
const titleLetterDate = computed(() =>
admissionType.value === '3' ? 'Tanggal Surat Kontrol' : 'Tanggal Surat Rujukan',
)
// Submit handler
const onSubmit = handleSubmit((values) => {
@@ -115,6 +126,23 @@ watch(props, (value) => {
/>
</Field>
</Cell>
<Cell>
<Label height="compact">Pelayanan</Label>
<Field :errMessage="errors.serviceType">
<Select
id="serviceType"
icon-name="i-lucide-chevron-down"
v-model="serviceType"
v-bind="serviceTypeAttrs"
:items="serviceTypes"
:disabled="isLoading || isReadonly"
placeholder="Pilih Pelayanan"
@change="emit('event', 'service-type', $event)"
/>
</Field>
</Cell>
<Cell>
<Label height="compact">Jalur</Label>
<Field :errMessage="errors.admissionType">
@@ -123,7 +151,7 @@ watch(props, (value) => {
icon-name="i-lucide-chevron-down"
v-model="admissionType"
v-bind="admissionTypeAttrs"
:items="admissionTypes"
:items="registerMethods"
:disabled="isLoading || isReadonly"
placeholder="Pilih jalur"
@change="emit('event', 'admission-type', $event)"
@@ -171,6 +199,7 @@ watch(props, (value) => {
/>
</Field>
</Cell>
<Cell>
<Label height="compact">
No. KTP
@@ -185,6 +214,7 @@ watch(props, (value) => {
/>
</Field>
</Cell>
<Cell>
<Label height="compact">
No. RM
@@ -199,6 +229,7 @@ watch(props, (value) => {
/>
</Field>
</Cell>
<Cell>
<Label height="compact">
Nama Pasien
@@ -213,6 +244,7 @@ watch(props, (value) => {
/>
</Field>
</Cell>
<Cell>
<Label height="compact">
No. Telepon
@@ -241,7 +273,7 @@ watch(props, (value) => {
>
<Cell>
<Label height="compact">
No. Surat Kontrol
{{ titleLetterNumber }}
<span class="text-red-500">*</span>
</Label>
<Field :errMessage="errors.referralLetterNumber">
@@ -268,9 +300,10 @@ watch(props, (value) => {
</div>
</Field>
</Cell>
<Cell>
<Label height="compact">
Tanggal Surat Kontrol
{{ titleLetterDate }}
<span class="text-red-500">*</span>
</Label>
<Field :errMessage="errors.referralLetterDate">
@@ -278,30 +311,11 @@ watch(props, (value) => {
id="referralLetterDate"
v-model="referralLetterDate"
v-bind="referralLetterDateAttrs"
:disabled="isLoading || isReadonly"
:disabled="true"
placeholder="Pilih tanggal surat kontrol"
/>
</Field>
</Cell>
<Cell>
<Label height="compact">
Klinik Tujuan
<span class="text-red-500">*</span>
</Label>
<Field :errMessage="errors.destinationClinic">
<Combobox
id="destinationClinic"
v-model="destinationClinic"
v-bind="destinationClinicAttrs"
:items="facilities"
:is-disabled="isLoading || isReadonly"
placeholder="Pilih Klinik"
search-placeholder="Cari Klinik"
empty-message="Item tidak ditemukan"
@update:searchText="emit('fetch', { menu: 'destinationClinic', value: $event })"
/>
</Field>
</Cell>
<Cell>
<Label height="compact">
@@ -332,6 +346,46 @@ watch(props, (value) => {
</Field>
</Cell>
<Cell>
<Label height="compact">
Faskes Asal
<span class="text-red-500">*</span>
</Label>
<Field :errMessage="errors.fromClinic">
<Combobox
id="fromClinic"
v-model="fromClinic"
v-bind="fromClinicAttrs"
:items="facilities"
:is-disabled="isLoading || isReadonly"
placeholder="Pilih Asal"
search-placeholder="Cari Asal"
empty-message="Item tidak ditemukan"
@update:searchText="emit('fetch', { menu: 'clinic', value: $event })"
/>
</Field>
</Cell>
<Cell>
<Label height="compact">
Klinik Tujuan
<span class="text-red-500">*</span>
</Label>
<Field :errMessage="errors.destinationClinic">
<Combobox
id="destinationClinic"
v-model="destinationClinic"
v-bind="destinationClinicAttrs"
:items="facilities"
:is-disabled="isLoading || isReadonly"
placeholder="Pilih Klinik"
search-placeholder="Cari Klinik"
empty-message="Item tidak ditemukan"
@update:searchText="emit('fetch', { menu: 'clinic', value: $event })"
/>
</Field>
</Cell>
<Cell>
<Label height="compact">
DPJP
@@ -347,7 +401,7 @@ watch(props, (value) => {
placeholder="Pilih DPJP"
search-placeholder="Cari DPJP"
empty-message="Item tidak ditemukan"
@update:searchText="emit('fetch', { menu: 'attendingDoctor', value: $event })"
@update:searchText="emit('fetch', { menu: 'doctor', value: $event })"
/>
</Field>
</Cell>
@@ -367,13 +421,13 @@ watch(props, (value) => {
placeholder="Pilih Diagnosa Awal"
search-placeholder="Cari Diagnosa Awal"
empty-message="Item tidak ditemukan"
@update:searchText="emit('fetch', { menu: 'initialDiagnosis', value: $event })"
@update:searchText="emit('fetch', { menu: 'diagnosis', value: $event })"
/>
</Field>
</Cell>
</Block>
<Block
<Block
labelSize="thin"
class="!pt-0"
:colCount="1"
@@ -392,12 +446,12 @@ watch(props, (value) => {
/>
</Field>
</Cell>
</Block>
</Block>
<Block
labelSize="thin"
class="!pt-0"
:colCount="2"
:colCount="3"
:cellFlex="false"
>
<Cell>
@@ -457,33 +511,29 @@ watch(props, (value) => {
</RadioGroup>
</Field>
</Cell>
</Block>
<Block
labelSize="thin"
class="!pt-0"
:colCount="3"
:cellFlex="false"
>
<Cell>
<Label height="compact">
Jenis Prosedur
Status Kecelakaan
<span class="text-red-500">*</span>
</Label>
<Field :errMessage="errors.procedureType">
<RadioGroup
v-model="procedureType"
class="flex items-center gap-2"
v-bind="procedureTypeAttrs"
>
<div class="flex items-center space-x-2">
<RadioGroupItem
value="0"
id="procedure-one"
/>
<Label for="procedure-one">Prosedur tidak berkelanjutan</Label>
</div>
<div class="flex items-center space-x-2">
<RadioGroupItem
value="1"
id="procedure-two"
/>
<Label for="procedure-two">Prosedur dan terapi berkelanjutan</Label>
</div>
</RadioGroup>
<Field :errMessage="errors.trafficAccident">
<Select
id="accident"
icon-name="i-lucide-chevron-down"
v-model="accident"
v-bind="accidentAttrs"
:items="accidents"
:disabled="isLoading || isReadonly"
placeholder="Pilih Status Kecelakaan"
/>
</Field>
</Cell>
</Block>
@@ -495,6 +545,60 @@ watch(props, (value) => {
:cellFlex="false"
>
<Cell>
<Label height="compact">
Tujuan Kunjungan
<span class="text-red-500">*</span>
</Label>
<Field :errMessage="errors.purposeOfVisit">
<Select
id="purposeOfVisit"
icon-name="i-lucide-chevron-down"
v-model="purposeOfVisit"
v-bind="purposeOfVisitAttrs"
:items="purposes"
:disabled="isLoading || isReadonly"
placeholder="Pilih Tujuan Kunjungan"
/>
</Field>
</Cell>
<Cell v-if="purposeOfVisit === '1'">
<Label height="compact">
Jenis Prosedur
<span class="text-red-500">*</span>
</Label>
<Field :errMessage="errors.procedureType">
<Select
id="procedureType"
icon-name="i-lucide-chevron-down"
v-model="procedureType"
v-bind="procedureTypeAttrs"
:items="procedures"
:disabled="isLoading || isReadonly"
placeholder="Pilih Jenis Prosedur"
/>
</Field>
</Cell>
<Cell v-if="purposeOfVisit === '2'">
<Label height="compact">
Assemen Pelayanan
<span class="text-red-500">*</span>
</Label>
<Field :errMessage="errors.serviceAssessment">
<Select
id="serviceAssessment"
icon-name="i-lucide-chevron-down"
v-model="serviceAssessment"
v-bind="serviceAssessmentAttrs"
:items="assessments"
:disabled="isLoading || isReadonly"
placeholder="Pilih Assemen Pelayanan"
/>
</Field>
</Cell>
<Cell v-if="purposeOfVisit === '1'">
<Label height="compact">
Kode Penunjang
<span class="text-red-500">*</span>
+54 -8
View File
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useRoute } from "vue-router"
import { useRoute } from 'vue-router'
// Components
import AppViewHistory from '~/components/app/sep/view-history.vue'
@@ -12,7 +12,7 @@ import type { SepHistoryData } from '~/components/app/sep/list-cfg.history'
import type { SepVisitData } from '~/components/app/sep/list-cfg.visit'
// Constants
import { supportCodes } from '~/lib/constants'
import { serviceTypes, serviceAssessments, registerMethods, trafficAccidents, supportCodes, procedureTypes, purposeOfVisits } from '~/lib/constants.vclaim'
// Services
import { getPatientDetail, getPatients } from '~/services/patient.service'
@@ -35,6 +35,7 @@ const selectedPatient = ref('')
const selectedPatientObject = ref<PatientEntity | null>(null)
const selectedLetter = ref('SK22334442')
const selectedObjects = ref<any>({})
const selectedServiceType = ref<string>('')
const histories = ref<Array<SepHistoryData>>([])
const visits = ref<Array<SepVisitData>>([])
const patients = ref<Array<{ id: string; identity: string; number: string; bpjs: string; name: string }>>([])
@@ -42,6 +43,12 @@ const doctors = ref<Array<{ value: string | number; label: string }>>([])
const diagnoses = ref<Array<{ value: string | number; label: string }>>([])
const facilities = ref<Array<{ value: string | number; label: string }>>([])
const supportCodesList = ref<Array<{ value: string; label: string }>>([])
const serviceTypesList = ref<Array<{ value: string; label: string }>>([])
const registerMethodsList = ref<Array<{ value: string; label: string }>>([])
const accidentsList = ref<Array<{ value: string; label: string }>>([])
const purposeOfVisitsList = ref<Array<{ value: string; label: string }>>([])
const proceduresList = ref<Array<{ value: string; label: string }>>([])
const assessmentsList = ref<Array<{ value: string; label: string }>>([])
const isPatientsLoading = ref(false)
const paginationMeta = ref<PaginationMeta>({
recordCount: 0,
@@ -246,18 +253,18 @@ function handleEvent(menu: string, value: any) {
async function handleFetch(params: any) {
const menu = params.menu || ''
const value = params.value || ''
if (menu === 'initialDiagnosis') {
if (menu === 'diagnosis') {
diagnoses.value = await getDiagnoseLabelList({ diagnosa: value })
}
if (menu === 'attendingDoctor') {
if (menu === 'doctor') {
console.log('value:', value)
}
if (menu === 'destinationClinic') {
if (menu === 'clinic') {
facilities.value = await getHealthFacilityLabelList({ faskes: value, 'jenis-faskes': 1 })
}
}
onMounted(async () => {
async function handleInit() {
diagnoses.value = await getDiagnoseLabelList({ diagnosa: 'paru' })
facilities.value = await getHealthFacilityLabelList({ faskes: 'Puskesmas', 'jenis-faskes': 1 })
doctors.value = await getDoctorLabelList({
@@ -265,6 +272,40 @@ onMounted(async () => {
'tgl-pelayanan': new Date().toISOString().substring(0, 10),
'kode-spesialis': 0,
})
serviceTypesList.value = Object.keys(serviceTypes).map((item) => ({
value: item.toString(),
label: serviceTypes[item],
})) as any
registerMethodsList.value = Object.keys(registerMethods)
.filter((item) => !['2', '4'].includes(item))
.map((item) => ({
value: item.toString(),
label: registerMethods[item],
})) as any
accidentsList.value = Object.keys(trafficAccidents).map((item) => ({
value: item.toString(),
label: trafficAccidents[item],
})) as any
purposeOfVisitsList.value = Object.keys(purposeOfVisits).map((item) => ({
value: item.toString(),
label: purposeOfVisits[item],
})) as any
proceduresList.value = Object.keys(procedureTypes).map((item) => ({
value: item.toString(),
label: procedureTypes[item],
})) as any
assessmentsList.value = Object.keys(serviceAssessments).map((item) => ({
value: item.toString(),
label: `${item.toString()} - ${serviceAssessments[item]}`,
})) as any
supportCodesList.value = Object.keys(supportCodes).map((item) => ({
value: item.toString(),
label: `${item.toString()} - ${supportCodes[item]}`,
})) as any
}
onMounted(async () => {
await handleInit()
getProvinceList().then((value) => {
console.log('value:', value)
})
@@ -277,7 +318,6 @@ onMounted(async () => {
getDiagnoseReferralList().then((value) => {
console.log('value:', value)
})
supportCodesList.value = Object.keys(supportCodes).map((item) => ({ value: item.toString(), label: supportCodes[item] })) as any
if (route.query) {
selectedObjects.value = { ...route.query }
}
@@ -298,8 +338,14 @@ onMounted(async () => {
:doctors="doctors"
:diagnoses="diagnoses"
:facilities="facilities"
:patient="selectedPatientObject"
:service-types="serviceTypesList"
:register-methods="registerMethodsList"
:accidents="accidentsList"
:purposes="purposeOfVisitsList"
:procedures="proceduresList"
:assessments="assessmentsList"
:support-codes="supportCodesList"
:patient="selectedPatientObject"
@fetch="handleFetch"
@event="handleEvent"
/>
-30
View File
@@ -366,33 +366,3 @@ export const medicalActionTypeCode: Record<string, string> = {
} as const
export type medicalActionTypeCodeKey = keyof typeof medicalActionTypeCode
export const lakaStatuses: Record<string, string> = {
'0': 'Bukan Kecelakaan lalu lintas [BKLL]',
'1': 'KLL dan Bukan Kecelakaan Kerja [BKK]',
'2': 'KLL',
'3': 'KK',
}
export const supportCodes: Record<string, string> = {
'1': 'Radioterapi',
'2': 'Kemoterapi',
'3': 'Rehabilitasi Medik',
'4': 'Rehabilitasi Psikososial',
'5': 'Transfusi Darah',
'6': 'Pelayanan Gigi',
'7': 'Laboratorium',
'8': 'USG',
'9': 'Farmasi',
'10': 'Lain-Lain',
'11': 'MRI',
'12': 'HEMODIALISA',
}
export const serviceAssessments: Record<string, string> = {
'1': 'Poli spesialis tidak tersedia pada hari sebelumnya',
'2': 'Jam Poli telah berakhir pada hari sebelumnya',
'3': 'Dokter Spesialis yang dimaksud tidak praktek pada hari sebelumnya',
'4': 'Atas Instruksi RS',
'5': 'Tujuan Kontrol',
}
+20 -15
View File
@@ -1,28 +1,33 @@
export const serviceTypes: Record<string,string> = {
'1': 'Rawat Inap',
'2': 'Rawat Jalan',
export const serviceTypes: Record<string, string> = {
'1': 'Rawat Inap',
'2': 'Rawat Jalan',
}
export const registerMethods: Record<string,string> = {
'1': 'Rujukan',
'2': 'IGD' ,
'3': 'Kontrol' ,
'4': 'Rujukan Internal' ,
export const registerMethods: Record<string, string> = {
'1': 'Rujukan',
'2': 'IGD',
'3': 'Kontrol',
'4': 'Rujukan Internal',
}
export const lakaStatuses: Record<string, string> = {
export const procedureTypes: Record<string, string> = {
'0': 'Prosedur tidak berkelanjutan',
'1': 'Prosedur dan terapi berkelanjutan',
}
export const purposeOfVisits: Record<string, string> = {
'0': 'Normal',
'1': 'Prosedur',
'2': 'Konsul Dokter',
}
export const trafficAccidents: Record<string, string> = {
'0': 'Bukan Kecelakaan lalu lintas [BKLL]',
'1': 'KLL dan Bukan Kecelakaan Kerja [BKK]',
'2': 'KLL',
'3': 'KK',
}
export const procedureTypes: Record<string, string> = {
'0': 'Normal',
'1': 'Prosedur',
'3': 'Konsul Dokter',
}
export const supportCodes: Record<string, string> = {
'1': 'Radioterapi',
'2': 'Kemoterapi',
+30 -2
View File
@@ -1,9 +1,11 @@
import { not } from "@vueuse/math"
import { not } from '@vueuse/math'
import { z } from 'zod'
import { serviceAssessments, serviceTypes } from '~/lib/constants.vclaim'
const ERROR_MESSAGES = {
required: {
sepDate: 'Tanggal wajib diisi',
serviceType: 'Jenis Pelayanan wajib diisi',
admissionType: 'Jenis Pendaftaran wajib diisi',
bpjsNumber: 'No. Kartu BPJS wajib diisi',
nationalId: 'Nomor ID wajib diisi',
@@ -12,6 +14,7 @@ const ERROR_MESSAGES = {
phoneNumber: 'Nomor Telepon wajib diisi',
referralLetterNumber: 'Nomor Surat Kontrol wajib diisi',
referralLetterDate: 'Tanggal Surat Kontrol wajib diisi',
fromClinic: 'Faskes Asal wajib diisi',
destinationClinic: 'Klinik Tujuan wajib diisi',
attendingDoctor: 'Dokter wajib diisi',
initialDiagnosis: 'Diagnosa Awal wajib diisi',
@@ -21,11 +24,18 @@ const ERROR_MESSAGES = {
procedureType: 'Jenis Prosedur wajib diisi',
supportCode: 'Kode Penunjang wajib diisi',
note: 'Catatan wajib diisi',
trafficAccident: 'Kejadian lalu lintas wajib diisi',
purposeOfVisit: 'Tujuan Kunjungan wajib diisi',
serviceAssessment: 'Assemen Pelayanan wajib diisi',
},
}
const IntegrationBpjsSchema = z.object({
sepDate: z.string({ required_error: ERROR_MESSAGES.required.sepDate }).min(1, ERROR_MESSAGES.required.sepDate),
serviceType: z
.string({ required_error: ERROR_MESSAGES.required.serviceType })
.min(1, ERROR_MESSAGES.required.serviceType)
.optional(),
admissionType: z
.string({ required_error: ERROR_MESSAGES.required.admissionType })
.min(1, ERROR_MESSAGES.required.admissionType),
@@ -50,6 +60,10 @@ const IntegrationBpjsSchema = z.object({
referralLetterDate: z
.string({ required_error: ERROR_MESSAGES.required.referralLetterDate })
.min(1, ERROR_MESSAGES.required.referralLetterDate),
fromClinic: z
.string({ required_error: ERROR_MESSAGES.required.fromClinic })
.min(1, ERROR_MESSAGES.required.fromClinic)
.optional(),
destinationClinic: z
.string({ required_error: ERROR_MESSAGES.required.destinationClinic })
.min(1, ERROR_MESSAGES.required.destinationClinic),
@@ -61,7 +75,9 @@ const IntegrationBpjsSchema = z.object({
.min(1, ERROR_MESSAGES.required.initialDiagnosis),
cob: z.string({ required_error: ERROR_MESSAGES.required.cob }).min(1, ERROR_MESSAGES.required.cob),
cataract: z.string({ required_error: ERROR_MESSAGES.required.cataract }).min(1, ERROR_MESSAGES.required.cataract),
clinicExcecutive: z.string({ required_error: ERROR_MESSAGES.required.clinicExcecutive }).min(1, ERROR_MESSAGES.required.clinicExcecutive),
clinicExcecutive: z
.string({ required_error: ERROR_MESSAGES.required.clinicExcecutive })
.min(1, ERROR_MESSAGES.required.clinicExcecutive),
procedureType: z
.string({ required_error: ERROR_MESSAGES.required.procedureType })
.min(1, ERROR_MESSAGES.required.procedureType),
@@ -69,6 +85,18 @@ const IntegrationBpjsSchema = z.object({
.string({ required_error: ERROR_MESSAGES.required.supportCode })
.min(1, ERROR_MESSAGES.required.supportCode),
note: z.string({ required_error: ERROR_MESSAGES.required.note }).min(1, ERROR_MESSAGES.required.note).optional(),
trafficAccident: z
.string({ required_error: ERROR_MESSAGES.required.trafficAccident })
.min(1, ERROR_MESSAGES.required.trafficAccident)
.optional(),
purposeOfVisit: z
.string({ required_error: ERROR_MESSAGES.required.purposeOfVisit })
.min(1, ERROR_MESSAGES.required.purposeOfVisit)
.optional(),
serviceAssessment: z
.string({ required_error: ERROR_MESSAGES.required.serviceAssessment })
.min(1, ERROR_MESSAGES.required.serviceAssessment)
.optional(),
})
type IntegrationBpjsFormData = z.infer<typeof IntegrationBpjsSchema>