Feat: Integrate Control Letter
This commit is contained in:
No files matched your search
@@ -98,7 +98,6 @@ function calculateAge(birthDate: string | Date | undefined): string {
|
|||||||
id="birthDate"
|
id="birthDate"
|
||||||
type="date"
|
type="date"
|
||||||
min="1900-01-01"
|
min="1900-01-01"
|
||||||
:max="new Date().toISOString().split('T')[0]"
|
|
||||||
v-bind="componentField"
|
v-bind="componentField"
|
||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
@update:model-value="
|
@update:model-value="
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ import type { FormErrors } from '~/types/error'
|
|||||||
import Combobox from '~/components/pub/my-ui/combobox/combobox.vue'
|
import Combobox from '~/components/pub/my-ui/combobox/combobox.vue'
|
||||||
import { cn, mapToComboboxOptList } from '~/lib/utils'
|
import { cn, mapToComboboxOptList } from '~/lib/utils'
|
||||||
import { occupationCodes } from '~/lib/constants'
|
import { occupationCodes } from '~/lib/constants'
|
||||||
|
import { getValueLabelList as getDoctorLabelList } from '~/services/doctor.service'
|
||||||
|
|
||||||
import * as DE from '~/components/pub/my-ui/doc-entry'
|
import * as DE from '~/components/pub/my-ui/doc-entry'
|
||||||
|
import type { Item } from '~/components/pub/my-ui/combobox'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
fieldName?: string
|
fieldName?: string
|
||||||
@@ -28,8 +30,28 @@ const {
|
|||||||
labelClass,
|
labelClass,
|
||||||
} = props
|
} = props
|
||||||
|
|
||||||
// Generate job options from constants, sama seperti pola genderCodes
|
const doctors = ref<Array<Item>>([])
|
||||||
const jobOptions = mapToComboboxOptList(occupationCodes)
|
|
||||||
|
async function fetchDpjp() {
|
||||||
|
doctors.value = await getDoctorLabelList({
|
||||||
|
serviceType: 1,
|
||||||
|
serviceDate: new Date().toISOString().substring(0, 10),
|
||||||
|
specialistCode: 0,
|
||||||
|
includes: 'employee-person',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedDpjpId = inject<Ref<string | null>>("selectedDpjpId")!
|
||||||
|
function handleDpjpChange(selected: string) {
|
||||||
|
console.log(`change dphp`)
|
||||||
|
selectedDpjpId.value = selected ?? null
|
||||||
|
const dpjp = doctors.value.find(s => s.value === selectedDpjpId.value)
|
||||||
|
console.log(dpjp)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchDpjp()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -56,10 +78,11 @@ const jobOptions = mapToComboboxOptList(occupationCodes)
|
|||||||
class="focus:ring-0 focus:ring-offset-0"
|
class="focus:ring-0 focus:ring-offset-0"
|
||||||
:id="fieldName"
|
:id="fieldName"
|
||||||
v-bind="componentField"
|
v-bind="componentField"
|
||||||
:items="jobOptions"
|
:items="doctors"
|
||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
search-placeholder="Cari..."
|
search-placeholder="Cari..."
|
||||||
empty-message="Data tidak ditemukan"
|
empty-message="Data tidak ditemukan"
|
||||||
|
@update:model-value="handleDpjpChange"
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
|
|||||||
@@ -3,11 +3,15 @@ import type { FormErrors } from '~/types/error'
|
|||||||
import Combobox from '~/components/pub/my-ui/combobox/combobox.vue'
|
import Combobox from '~/components/pub/my-ui/combobox/combobox.vue'
|
||||||
import { cn, mapToComboboxOptList } from '~/lib/utils'
|
import { cn, mapToComboboxOptList } from '~/lib/utils'
|
||||||
import { occupationCodes } from '~/lib/constants'
|
import { occupationCodes } from '~/lib/constants'
|
||||||
|
import type { Item } from '~/components/pub/my-ui/combobox'
|
||||||
|
import { getValueLabelList as getSpecialistLabelList } from '~/services/specialist.service'
|
||||||
|
import { getValueLabelList as getSubspecialistLabelList } from '~/services/subspecialist.service'
|
||||||
|
|
||||||
import * as DE from '~/components/pub/my-ui/doc-entry'
|
import * as DE from '~/components/pub/my-ui/doc-entry'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
fieldName?: string
|
specialistFieldName?: string
|
||||||
|
subSpecialistFieldName?: string
|
||||||
label?: string
|
label?: string
|
||||||
placeholder?: string
|
placeholder?: string
|
||||||
errors?: FormErrors
|
errors?: FormErrors
|
||||||
@@ -19,7 +23,8 @@ const props = defineProps<{
|
|||||||
}>()
|
}>()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
fieldName = 'job',
|
specialistFieldName = 'job',
|
||||||
|
subSpecialistFieldName = 'job',
|
||||||
label = 'Pekerjaan',
|
label = 'Pekerjaan',
|
||||||
placeholder = 'Pilih pekerjaan',
|
placeholder = 'Pilih pekerjaan',
|
||||||
errors,
|
errors,
|
||||||
@@ -28,43 +33,103 @@ const {
|
|||||||
labelClass,
|
labelClass,
|
||||||
} = props
|
} = props
|
||||||
|
|
||||||
// Generate job options from constants, sama seperti pola genderCodes
|
const specialists = ref<Array<Item>>([])
|
||||||
const jobOptions = mapToComboboxOptList(occupationCodes)
|
const subspecialists = ref<Array<Item>>([])
|
||||||
|
|
||||||
|
async function fetchSpecialists() {
|
||||||
|
specialists.value = await getSpecialistLabelList({
|
||||||
|
serviceType: 1,
|
||||||
|
serviceDate: new Date().toISOString().substring(0, 10),
|
||||||
|
specialistCode: 0,
|
||||||
|
})
|
||||||
|
subspecialists.value = await getSubspecialistLabelList({
|
||||||
|
serviceType: 1,
|
||||||
|
serviceDate: new Date().toISOString().substring(0, 10),
|
||||||
|
specialistCode: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedSpecialistId = inject<Ref<string | null>>("selectedSpecialistId")!
|
||||||
|
function handleSpecialistChange(selected: string) {
|
||||||
|
selectedSpecialistId.value = selected ?? null
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchSpecialists()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<DE.Cell :class="cn('select-field-group', fieldGroupClass, containerClass)">
|
<DE.Block :col-count="2" :class="cn('select-field-group', fieldGroupClass, containerClass)">
|
||||||
<DE.Label
|
<div>
|
||||||
:label-for="fieldName"
|
<DE.Label
|
||||||
|
:label-for="specialistFieldName"
|
||||||
:class="cn('select-field-label', labelClass)"
|
:class="cn('select-field-label', labelClass)"
|
||||||
:is-required="isRequired"
|
:is-required="isRequired"
|
||||||
>
|
>
|
||||||
{{ label }}
|
Spesialis
|
||||||
</DE.Label>
|
</DE.Label>
|
||||||
<DE.Field
|
<DE.Field
|
||||||
:id="fieldName"
|
:id="specialistFieldName"
|
||||||
:errors="errors"
|
:errors="errors"
|
||||||
:class="cn('select-field-wrapper')"
|
:class="cn('select-field-wrapper')"
|
||||||
>
|
>
|
||||||
<FormField
|
<FormField
|
||||||
v-slot="{ componentField }"
|
v-slot="{ componentField }"
|
||||||
:name="fieldName"
|
:name="specialistFieldName"
|
||||||
>
|
>
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Combobox
|
<Combobox
|
||||||
class="focus:ring-0 focus:ring-offset-0"
|
class="focus:ring-0 focus:ring-offset-0"
|
||||||
:id="fieldName"
|
:id="specialistFieldName"
|
||||||
v-bind="componentField"
|
v-bind="componentField"
|
||||||
:items="jobOptions"
|
:items="specialists"
|
||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
search-placeholder="Cari..."
|
search-placeholder="Cari..."
|
||||||
empty-message="Data tidak ditemukan"
|
empty-message="Data tidak ditemukan"
|
||||||
|
@update:model-value="handleSpecialistChange"
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</FormField>
|
</FormField>
|
||||||
</DE.Field>
|
</DE.Field>
|
||||||
</DE.Cell>
|
</div>
|
||||||
|
<div>
|
||||||
|
<DE.Label
|
||||||
|
:label-for="subSpecialistFieldName"
|
||||||
|
:class="cn('select-field-label', labelClass)"
|
||||||
|
:is-required="isRequired"
|
||||||
|
>
|
||||||
|
Sub Spesialis
|
||||||
|
</DE.Label>
|
||||||
|
<DE.Field
|
||||||
|
:id="subSpecialistFieldName"
|
||||||
|
:errors="errors"
|
||||||
|
:class="cn('select-field-wrapper')"
|
||||||
|
>
|
||||||
|
<FormField
|
||||||
|
v-slot="{ componentField }"
|
||||||
|
:name="subSpecialistFieldName"
|
||||||
|
>
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<Combobox
|
||||||
|
class="focus:ring-0 focus:ring-offset-0"
|
||||||
|
:id="subSpecialistFieldName"
|
||||||
|
v-bind="componentField"
|
||||||
|
:items="subspecialists"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
search-placeholder="Cari..."
|
||||||
|
empty-message="Data tidak ditemukan"
|
||||||
|
:is-disabled="!selectedSpecialistId"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</DE.Field>
|
||||||
|
</div>
|
||||||
|
</DE.Block>
|
||||||
</template>
|
</template>
|
||||||
@@ -45,20 +45,21 @@ defineExpose({
|
|||||||
:is-disabled="true"
|
:is-disabled="true"
|
||||||
/>
|
/>
|
||||||
<SelectDate
|
<SelectDate
|
||||||
field-name="controlDate"
|
field-name="date"
|
||||||
label="Tanggal Rencana Kontrol"
|
label="Tanggal Rencana Kontrol"
|
||||||
:errors="errors"
|
:errors="errors"
|
||||||
is-required
|
is-required
|
||||||
/>
|
/>
|
||||||
<SelectSpeciality
|
<SelectSpeciality
|
||||||
field-name="SpesialisSubSpesialis"
|
specialist-field-name="spesialist_id"
|
||||||
|
sub-specialist-field-name="subspesialist_id"
|
||||||
label="Spesialis/Sub Spesialis"
|
label="Spesialis/Sub Spesialis"
|
||||||
placeholder="Pilih Spesialis/Sub Spesialis"
|
placeholder="Pilih Spesialis/Sub Spesialis"
|
||||||
:errors="errors"
|
:errors="errors"
|
||||||
is-required
|
is-required
|
||||||
/>
|
/>
|
||||||
<SelectDpjp
|
<SelectDpjp
|
||||||
field-name="dpjp"
|
field-name="doctor_id"
|
||||||
label="DPJP"
|
label="DPJP"
|
||||||
placeholder="Pilih DPJP"
|
placeholder="Pilih DPJP"
|
||||||
:errors="errors"
|
:errors="errors"
|
||||||
|
|||||||
@@ -7,19 +7,20 @@ import { calculateAge } from '~/lib/utils'
|
|||||||
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'))
|
||||||
|
|
||||||
export const config: Config = {
|
export const config: Config = {
|
||||||
cols: [{}, {}, {}, {}, {width: 3},],
|
cols: [{width: 180}, {}, {}, {}, {}, {width: 30},],
|
||||||
|
|
||||||
headers: [
|
headers: [
|
||||||
[
|
[
|
||||||
{ label: 'Tgl Rencana Kontrol' },
|
{ label: 'Tgl Rencana Kontrol' },
|
||||||
{ label: 'Spesialis/Sub Spesialis' },
|
{ label: 'Spesialis' },
|
||||||
|
{ label: 'Sub Spesialis' },
|
||||||
{ label: 'DPJP' },
|
{ label: 'DPJP' },
|
||||||
{ label: 'Status SEP' },
|
{ label: 'Status SEP' },
|
||||||
{ label: 'Action' },
|
{ label: 'Action' },
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
keys: ['birth_date', 'number', 'person.name', 'birth_date', 'action'],
|
keys: ['date', 'specialist.name', 'subspecialist.name', 'doctor.employee.person.name', 'sep_status', 'action'],
|
||||||
|
|
||||||
delKeyNames: [
|
delKeyNames: [
|
||||||
{ key: 'code', label: 'Kode' },
|
{ key: 'code', label: 'Kode' },
|
||||||
@@ -27,50 +28,20 @@ export const config: Config = {
|
|||||||
],
|
],
|
||||||
|
|
||||||
parses: {
|
parses: {
|
||||||
patientId: (rec: unknown): unknown => {
|
date: (rec: unknown): unknown => {
|
||||||
const patient = rec as Patient
|
const date = (rec as any).date
|
||||||
return patient.number
|
if (typeof date == 'object' && date) {
|
||||||
},
|
return (date as Date).toLocaleDateString('id-ID')
|
||||||
identity_number: (rec: unknown): unknown => {
|
} else if (typeof date == 'string') {
|
||||||
const { person } = rec as Patient
|
return (date as string).substring(0, 10)
|
||||||
|
|
||||||
if (person.nationality == 'WNA') {
|
|
||||||
return person.passportNumber
|
|
||||||
}
|
}
|
||||||
|
return date
|
||||||
return person.residentIdentityNumber || '-'
|
|
||||||
},
|
},
|
||||||
birth_date: (rec: unknown): unknown => {
|
specialist_subspecialist: (rec: unknown): unknown => {
|
||||||
const { person } = rec as Patient
|
|
||||||
|
|
||||||
if (typeof person.birthDate == 'object' && person.birthDate) {
|
|
||||||
return (person.birthDate as Date).toLocaleDateString('id-ID')
|
|
||||||
} else if (typeof person.birthDate == 'string') {
|
|
||||||
return (person.birthDate as string).substring(0, 10)
|
|
||||||
}
|
|
||||||
return person.birthDate
|
|
||||||
},
|
|
||||||
patient_age: (rec: unknown): unknown => {
|
|
||||||
const { person } = rec as Patient
|
|
||||||
return calculateAge(person.birthDate)
|
|
||||||
},
|
|
||||||
gender: (rec: unknown): unknown => {
|
|
||||||
const { person } = rec as Patient
|
|
||||||
|
|
||||||
if (typeof person.gender_code == 'number' && person.gender_code >= 0) {
|
|
||||||
return person.gender_code
|
|
||||||
} else if (typeof person.gender_code === 'string' && person.gender_code) {
|
|
||||||
return genderCodes[person.gender_code] || '-'
|
|
||||||
}
|
|
||||||
return '-'
|
return '-'
|
||||||
},
|
},
|
||||||
education: (rec: unknown): unknown => {
|
dpjp: (rec: unknown): unknown => {
|
||||||
const { person } = rec as Patient
|
// const { person } = rec as Patient
|
||||||
if (typeof person.education_code == 'number' && person.education_code >= 0) {
|
|
||||||
return person.education_code
|
|
||||||
} else if (typeof person.education_code === 'string' && person.education_code) {
|
|
||||||
return educationCodes[person.education_code] || '-'
|
|
||||||
}
|
|
||||||
return '-'
|
return '-'
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -86,8 +57,8 @@ export const config: Config = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
htmls: {
|
htmls: {
|
||||||
patient_address(_rec) {
|
sep_status(_rec) {
|
||||||
return '-'
|
return 'SEP Internal'
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -1,32 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import type { Patient, genPatientProps } from '~/models/patient'
|
|
||||||
import type { ExposedForm } from '~/types/form'
|
import type { ExposedForm } from '~/types/form'
|
||||||
import type { PatientBase } from '~/models/patient'
|
|
||||||
import Action from '~/components/pub/my-ui/nav-footer/ba-dr-su.vue'
|
import Action from '~/components/pub/my-ui/nav-footer/ba-dr-su.vue'
|
||||||
import { genPatient } from '~/models/patient'
|
|
||||||
import { PatientSchema } from '~/schemas/patient.schema'
|
|
||||||
import { PersonAddressRelativeSchema } from '~/schemas/person-address-relative.schema'
|
|
||||||
import { PersonAddressSchema } from '~/schemas/person-address.schema'
|
|
||||||
import { PersonContactListSchema } from '~/schemas/person-contact.schema'
|
|
||||||
import { PersonFamiliesSchema } from '~/schemas/person-family.schema'
|
|
||||||
import { ResponsiblePersonSchema } from '~/schemas/person-relative.schema'
|
|
||||||
import { uploadAttachment } from '~/services/patient.service'
|
|
||||||
import { ControlLetterSchema } from '~/schemas/control-letter.schema'
|
import { ControlLetterSchema } from '~/schemas/control-letter.schema'
|
||||||
|
import { handleActionSave,} from '~/handlers/control-letter.handler'
|
||||||
import {
|
|
||||||
// for form entry
|
|
||||||
isReadonly,
|
|
||||||
isProcessing,
|
|
||||||
isFormEntryDialogOpen,
|
|
||||||
isRecordConfirmationOpen,
|
|
||||||
onResetState,
|
|
||||||
handleActionSave,
|
|
||||||
handleCancelForm,
|
|
||||||
} from '~/handlers/patient.handler'
|
|
||||||
|
|
||||||
import { toast } from '~/components/pub/ui/toast'
|
import { toast } from '~/components/pub/ui/toast'
|
||||||
import Confirmation from '~/components/pub/my-ui/confirmation/confirmation.vue'
|
import Confirmation from '~/components/pub/my-ui/confirmation/confirmation.vue'
|
||||||
|
import { type ControlLetter } from '~/models/control-letter'
|
||||||
|
|
||||||
// #region Props & Emits
|
// #region Props & Emits
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@@ -34,12 +15,16 @@ const props = defineProps<{
|
|||||||
}>()
|
}>()
|
||||||
|
|
||||||
// form related state
|
// form related state
|
||||||
const personPatientForm = ref<ExposedForm<any> | null>(null)
|
const route = useRoute()
|
||||||
|
const encounterId = typeof route.params.id == 'string' ? parseInt(route.params.id) : 0
|
||||||
|
const controlLetterForm = ref<ExposedForm<any> | null>(null)
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
// #region State & Computed
|
// #region State & Computed
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const isConfirmationOpen = ref(false)
|
const isConfirmationOpen = ref(false)
|
||||||
|
const selectedSpecialistId = ref<number|null>(null)
|
||||||
|
const selectedDpjpId = ref<number|null>(null)
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
// #region Lifecycle Hooks
|
// #region Lifecycle Hooks
|
||||||
@@ -51,12 +36,42 @@ function goBack() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleConfirmAdd() {
|
async function handleConfirmAdd() {
|
||||||
// handleActionClick('submit')
|
const controlLetter: ControlLetter = await composeFormData()
|
||||||
console.log(`tersubmit wak`)
|
let createdControlLetterId = 0
|
||||||
|
|
||||||
|
const response = await handleActionSave(
|
||||||
|
controlLetter,
|
||||||
|
() => { },
|
||||||
|
() => { },
|
||||||
|
toast,
|
||||||
|
)
|
||||||
|
|
||||||
|
const data = (response?.body?.data ?? null)
|
||||||
|
if (!data) return
|
||||||
|
createdControlLetterId = data.id
|
||||||
|
|
||||||
|
// // If has callback provided redirect to callback with patientData
|
||||||
|
if (props.callbackUrl) {
|
||||||
|
navigateTo(props.callbackUrl + '?control-letter-id=' + controlLetter.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
goBack()
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCancelAdd() {
|
async function composeFormData(): Promise<ControlLetter> {
|
||||||
isConfirmationOpen.value = false
|
const [controlLetter,] = await Promise.all([
|
||||||
|
controlLetterForm.value?.validate(),
|
||||||
|
])
|
||||||
|
|
||||||
|
const results = [controlLetter]
|
||||||
|
const allValid = results.every((r) => r?.valid)
|
||||||
|
|
||||||
|
// exit, if form errors happend during validation
|
||||||
|
if (!allValid) return Promise.reject('Form validation failed')
|
||||||
|
|
||||||
|
const formData = controlLetter?.values
|
||||||
|
formData.encounter_id = encounterId
|
||||||
|
return new Promise((resolve) => resolve(formData))
|
||||||
}
|
}
|
||||||
// #endregion region
|
// #endregion region
|
||||||
|
|
||||||
@@ -64,29 +79,6 @@ function handleCancelAdd() {
|
|||||||
async function handleActionClick(eventType: string) {
|
async function handleActionClick(eventType: string) {
|
||||||
if (eventType === 'submit') {
|
if (eventType === 'submit') {
|
||||||
isConfirmationOpen.value = true
|
isConfirmationOpen.value = true
|
||||||
// const patient: Patient = await composeFormData()
|
|
||||||
// let createdPatientId = 0
|
|
||||||
|
|
||||||
// const response = await handleActionSave(
|
|
||||||
// patient,
|
|
||||||
// () => {},
|
|
||||||
// () => {},
|
|
||||||
// toast,
|
|
||||||
// )
|
|
||||||
|
|
||||||
// const data = (response?.body?.data ?? null) as PatientBase | null
|
|
||||||
// if (!data) return
|
|
||||||
// createdPatientId = data.id
|
|
||||||
|
|
||||||
// If has callback provided redirect to callback with patientData
|
|
||||||
// if (props.callbackUrl) {
|
|
||||||
// await navigateTo(props.callbackUrl + '?patient-id=' + patient.id)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Navigate to patient list or show success message
|
|
||||||
// await navigateTo('/outpatient/encounter')
|
|
||||||
// return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (eventType === 'cancel') {
|
if (eventType === 'cancel') {
|
||||||
@@ -96,9 +88,15 @@ async function handleActionClick(eventType: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
goBack()
|
goBack()
|
||||||
// handleCancelForm()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleCancelAdd() {
|
||||||
|
isConfirmationOpen.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
provide("selectedSpecialistId", selectedSpecialistId);
|
||||||
|
provide("selectedDpjpId", selectedDpjpId);
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
// #region Watchers
|
// #region Watchers
|
||||||
@@ -108,22 +106,19 @@ async function handleActionClick(eventType: string) {
|
|||||||
<template>
|
<template>
|
||||||
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg font-semibold xl:text-xl">Tambah Surat Kontrol</div>
|
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg font-semibold xl:text-xl">Tambah Surat Kontrol</div>
|
||||||
<AppOutpatientEncounterEntryForm
|
<AppOutpatientEncounterEntryForm
|
||||||
ref="personPatientForm"
|
ref="controlLetterForm"
|
||||||
:schema="ControlLetterSchema"
|
:schema="ControlLetterSchema" />
|
||||||
/>
|
|
||||||
|
|
||||||
<div class="my-2 flex justify-end py-2">
|
<div class="my-2 flex justify-end py-2">
|
||||||
<Action :enable-draft="false" @click="handleActionClick" />
|
<Action :enable-draft="false" @click="handleActionClick" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Confirmation
|
<Confirmation v-model:open="isConfirmationOpen"
|
||||||
v-model:open="isConfirmationOpen"
|
|
||||||
title="Simpan Data"
|
title="Simpan Data"
|
||||||
message="Apakah Anda yakin ingin menyimpan data ini?"
|
message="Apakah Anda yakin ingin menyimpan data ini?"
|
||||||
confirm-text="Simpan"
|
confirm-text="Simpan"
|
||||||
@confirm="handleConfirmAdd"
|
@confirm="handleConfirmAdd"
|
||||||
@cancel="handleCancelAdd"
|
@cancel="handleCancelAdd" />
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -4,12 +4,11 @@ import { withBase } from '~/models/_base'
|
|||||||
import type { HeaderPrep } from '~/components/pub/my-ui/data/types'
|
import type { HeaderPrep } from '~/components/pub/my-ui/data/types'
|
||||||
import type { Patient } from '~/models/patient'
|
import type { Patient } from '~/models/patient'
|
||||||
import type { Person } from '~/models/person'
|
import type { Person } from '~/models/person'
|
||||||
|
import { getDetail } from '~/services/control-letter.service'
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import Header from '~/components/pub/my-ui/nav-header/prep.vue'
|
import Header from '~/components/pub/my-ui/nav-header/prep.vue'
|
||||||
|
|
||||||
import { getPatientDetail } from '~/services/patient.service'
|
|
||||||
|
|
||||||
// #region Props & Emits
|
// #region Props & Emits
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
patientId: number
|
patientId: number
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { PersonContactListSchema } from '~/schemas/person-contact.schema'
|
|||||||
import { PersonFamiliesSchema } from '~/schemas/person-family.schema'
|
import { PersonFamiliesSchema } from '~/schemas/person-family.schema'
|
||||||
import { ResponsiblePersonSchema } from '~/schemas/person-relative.schema'
|
import { ResponsiblePersonSchema } from '~/schemas/person-relative.schema'
|
||||||
import { uploadAttachment } from '~/services/patient.service'
|
import { uploadAttachment } from '~/services/patient.service'
|
||||||
|
import { getDetail, update } from '~/services/control-letter.service'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
// for form entry
|
// for form entry
|
||||||
|
|||||||
@@ -1,24 +1,23 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { HeaderPrep, RefSearchNav } from '~/components/pub/my-ui/data/types'
|
import type { HeaderPrep, RefSearchNav } from '~/components/pub/my-ui/data/types'
|
||||||
import type { Summary } from '~/components/pub/my-ui/summary-card/type'
|
|
||||||
|
|
||||||
// #region Imports
|
// #region Imports
|
||||||
import { Calendar, Hospital, UserCheck, UsersRound } from 'lucide-vue-next'
|
|
||||||
import RecordConfirmation from '~/components/pub/my-ui/confirmation/record-confirmation.vue'
|
import RecordConfirmation from '~/components/pub/my-ui/confirmation/record-confirmation.vue'
|
||||||
import { ActionEvents } from '~/components/pub/my-ui/data/types'
|
import { ActionEvents } from '~/components/pub/my-ui/data/types'
|
||||||
|
|
||||||
import Header from '~/components/pub/my-ui/nav-header/prep.vue'
|
import Header from '~/components/pub/my-ui/nav-header/prep.vue'
|
||||||
import SummaryCard from '~/components/pub/my-ui/summary-card/summary-card.vue'
|
|
||||||
import { usePaginatedList } from '~/composables/usePaginatedList'
|
import { usePaginatedList } from '~/composables/usePaginatedList'
|
||||||
|
import { getList, remove } from '~/services/control-letter.service'
|
||||||
import { getPatients, removePatient } from '~/services/patient.service'
|
import { toast } from '~/components/pub/ui/toast'
|
||||||
|
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
// #region State
|
// #region State
|
||||||
|
const route = useRoute()
|
||||||
|
const encounterId = typeof route.params.id == 'string' ? parseInt(route.params.id) : 0
|
||||||
|
|
||||||
const { data, isLoading, paginationMeta, searchInput, handlePageChange, handleSearch, fetchData } = usePaginatedList({
|
const { data, isLoading, paginationMeta, searchInput, handlePageChange, handleSearch, fetchData } = usePaginatedList({
|
||||||
fetchFn: (params) => getPatients({ ...params, includes: ['person', 'person-Addresses'] }),
|
fetchFn: (params) => getList({ ...params, includes: 'specialist,subspecialist,doctor-employee-person', }),
|
||||||
entityName: 'patient',
|
entityName: 'control-letter',
|
||||||
})
|
})
|
||||||
|
|
||||||
const refSearchNav: RefSearchNav = {
|
const refSearchNav: RefSearchNav = {
|
||||||
@@ -45,24 +44,27 @@ const headerPrep: HeaderPrep = {
|
|||||||
icon: 'i-lucide-newspaper',
|
icon: 'i-lucide-newspaper',
|
||||||
addNav: {
|
addNav: {
|
||||||
label: "Surat Kontrol",
|
label: "Surat Kontrol",
|
||||||
onClick: () => navigateTo('/outpatient/encounter/add'),
|
onClick: () => navigateTo({
|
||||||
|
name: 'rehab-encounter-id-control-letter-add',
|
||||||
|
params: { id: encounterId },
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
// #region Lifecycle Hooks
|
// #region Lifecycle Hooks
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getPatientSummary()
|
getListData()
|
||||||
})
|
})
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
// #region Functions
|
// #region Functions
|
||||||
async function getPatientSummary() {
|
async function getListData() {
|
||||||
try {
|
try {
|
||||||
summaryLoading.value = true
|
summaryLoading.value = true
|
||||||
await new Promise((resolve) => setTimeout(resolve, 500))
|
await new Promise((resolve) => setTimeout(resolve, 500))
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching patient summary:', error)
|
console.error('Error fetching Data:', error)
|
||||||
} finally {
|
} finally {
|
||||||
summaryLoading.value = false
|
summaryLoading.value = false
|
||||||
}
|
}
|
||||||
@@ -70,22 +72,17 @@ async function getPatientSummary() {
|
|||||||
|
|
||||||
// Handle confirmation result
|
// Handle confirmation result
|
||||||
async function handleConfirmDelete(record: any, action: string) {
|
async function handleConfirmDelete(record: any, action: string) {
|
||||||
console.log('Confirmed action:', action, 'for record:', record)
|
|
||||||
|
|
||||||
if (action === 'delete' && record?.id) {
|
if (action === 'delete' && record?.id) {
|
||||||
try {
|
try {
|
||||||
const result = await removePatient(record.id)
|
const result = await remove(record.id)
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
console.log('Patient deleted successfully')
|
toast({ title: 'Berhasil', description: 'Data berhasil dihapus', variant: 'default' })
|
||||||
// Refresh the list
|
|
||||||
await fetchData()
|
await fetchData()
|
||||||
} else {
|
} else {
|
||||||
console.error('Failed to delete patient:', result)
|
toast({ title: 'Gagal', description: `Data gagal dihapus`, variant: 'destructive' })
|
||||||
// Handle error - show error message to user
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error deleting patient:', error)
|
toast({ title: 'Gagal', description: `Something went wrong`, variant: 'destructive' })
|
||||||
// Handle error - show error message to user
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -134,28 +131,10 @@ watch([recId, recAction], () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Header v-model:search="searchInput" :prep="{ ...headerPrep }" :ref-search-nav="refSearchNav" />
|
<Header v-model:search="searchInput"
|
||||||
|
:prep="{ ...headerPrep }"
|
||||||
<!-- Disable dulu, ayahab kalo diminta beneran -->
|
:ref-search-nav="refSearchNav"
|
||||||
<!-- <div class="my-4 flex flex-1 flex-col gap-4 md:gap-8">
|
@search="handleSearch" />
|
||||||
<div class="grid gap-4 md:grid-cols-2 md:gap-8 lg:grid-cols-4">
|
|
||||||
<template v-if="summaryLoading">
|
|
||||||
<SummaryCard
|
|
||||||
v-for="n in 4"
|
|
||||||
:key="n"
|
|
||||||
is-skeleton
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<SummaryCard
|
|
||||||
v-for="card in summaryData"
|
|
||||||
:key="card.title"
|
|
||||||
:stat="card"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
-->
|
|
||||||
|
|
||||||
<AppOutpatientEncounterList :data="data" :pagination-meta="paginationMeta" @page-change="handlePageChange" />
|
<AppOutpatientEncounterList :data="data" :pagination-meta="paginationMeta" @page-change="handlePageChange" />
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// Handlers
|
||||||
|
import { genCrudHandler } from '~/handlers/_handler'
|
||||||
|
|
||||||
|
// Services
|
||||||
|
import { create, update, remove } from '~/services/control-letter.service'
|
||||||
|
|
||||||
|
export const {
|
||||||
|
recId,
|
||||||
|
recAction,
|
||||||
|
recItem,
|
||||||
|
isReadonly,
|
||||||
|
isProcessing,
|
||||||
|
isFormEntryDialogOpen,
|
||||||
|
isRecordConfirmationOpen,
|
||||||
|
onResetState,
|
||||||
|
handleActionSave,
|
||||||
|
handleActionEdit,
|
||||||
|
handleActionRemove,
|
||||||
|
handleCancelForm,
|
||||||
|
} = genCrudHandler({
|
||||||
|
create,
|
||||||
|
update,
|
||||||
|
remove,
|
||||||
|
})
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { type Base, genBase } from "./_base"
|
||||||
|
|
||||||
|
export interface ControlLetter extends Base {
|
||||||
|
sep_status: string
|
||||||
|
control_plan_date: string
|
||||||
|
specialist_sub_specialist_id: string
|
||||||
|
dpjp_id: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function genControlLetter(): ControlLetter {
|
||||||
|
return {
|
||||||
|
...genBase(),
|
||||||
|
sep_status: '',
|
||||||
|
control_plan_date: '',
|
||||||
|
specialist_sub_specialist_id: '',
|
||||||
|
dpjp_id: '',
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { PagePermission } from '~/models/role'
|
||||||
|
import Error from '~/components/pub/my-ui/error/error.vue'
|
||||||
|
import { PAGE_PERMISSIONS } from '~/lib/page-permission'
|
||||||
|
|
||||||
|
definePageMeta({
|
||||||
|
middleware: ['rbac'],
|
||||||
|
roles: ['doctor', 'nurse', 'admisi', 'pharmacy', 'billing', 'management'],
|
||||||
|
title: 'Tambah Surat Kontrol',
|
||||||
|
contentFrame: 'cf-full-width',
|
||||||
|
})
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
useHead({
|
||||||
|
title: () => route.meta.title as string,
|
||||||
|
})
|
||||||
|
|
||||||
|
const roleAccess: PagePermission = PAGE_PERMISSIONS['/patient']
|
||||||
|
|
||||||
|
const { checkRole, hasReadAccess } = useRBAC()
|
||||||
|
|
||||||
|
// Check if user has access to this page
|
||||||
|
const hasAccess = checkRole(roleAccess)
|
||||||
|
// if (!hasAccess) {
|
||||||
|
// navigateTo('/403')
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Define permission-based computed properties
|
||||||
|
// const canRead = hasReadAccess(roleAccess)
|
||||||
|
const canRead = true
|
||||||
|
const callbackUrl = route.query['return-path'] as string | undefined
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div v-if="canRead">
|
||||||
|
<ContentOutpatientEncounterAdd :callback-url="callbackUrl" />
|
||||||
|
</div>
|
||||||
|
<Error v-else :status-code="403" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -3,14 +3,19 @@ import { z } from 'zod'
|
|||||||
const ControlLetterSchema = z.object({
|
const ControlLetterSchema = z.object({
|
||||||
sepStatus: z.string({
|
sepStatus: z.string({
|
||||||
required_error: 'Mohon isi status SEP',
|
required_error: 'Mohon isi status SEP',
|
||||||
|
}).default('SEP Internal'),
|
||||||
|
spesialist_id: z.number({
|
||||||
|
required_error: 'Mohon isi Spesialis/Sub Spesialis',
|
||||||
}),
|
}),
|
||||||
SpesialisSubSpesialis: z.string({
|
subspesialist_id: z.number({
|
||||||
required_error: 'Mohon isi status Spesialis/Sub Spesialis',
|
required_error: 'Mohon isi Spesialis/Sub Spesialis',
|
||||||
}),
|
}),
|
||||||
dpjp: z.string({
|
doctor_id: z.number({
|
||||||
required_error: 'Mohon isi status DPJP',
|
required_error: 'Mohon isi DPJP',
|
||||||
}),
|
}),
|
||||||
controlDate: z.string({
|
encounter_id: z.number().optional(),
|
||||||
|
unit_id: z.number().optional(),
|
||||||
|
date: z.string({
|
||||||
required_error: 'Mohon lengkapi Tanggal Kontrol',
|
required_error: 'Mohon lengkapi Tanggal Kontrol',
|
||||||
})
|
})
|
||||||
.refine(
|
.refine(
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
// Base
|
||||||
|
import * as base from './_crud-base'
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
import { encounterClassCodes } from '~/lib/constants'
|
||||||
|
|
||||||
|
const path = '/api/v1/control-letter'
|
||||||
|
const name = 'control-letter'
|
||||||
|
|
||||||
|
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, params?: any) {
|
||||||
|
return base.getDetail(path, id, name, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
// Base
|
||||||
|
import * as base from './_crud-base'
|
||||||
|
|
||||||
|
// Types
|
||||||
|
import type { Doctor } from '~/models/doctor'
|
||||||
|
|
||||||
|
const path = '/api/v1/doctor'
|
||||||
|
const name = 'doctor'
|
||||||
|
|
||||||
|
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): 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: Doctor) => ({
|
||||||
|
value: item.id ? Number(item.id) : item.id,
|
||||||
|
label: item.employee.person.name,
|
||||||
|
parent: item.unit_id ? Number(item.unit_id) : null,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user