Feat: Integrate Control Letter

This commit is contained in:
hasyim_kai
2025-11-04 16:56:08 +07:00
parent 2275f4dc99
commit 331f4a6b20
15 changed files with 365 additions and 173 deletions
@@ -1,32 +1,13 @@
<script setup lang="ts">
import { useRouter } from 'vue-router'
import type { Patient, genPatientProps } from '~/models/patient'
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 { 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 {
// for form entry
isReadonly,
isProcessing,
isFormEntryDialogOpen,
isRecordConfirmationOpen,
onResetState,
handleActionSave,
handleCancelForm,
} from '~/handlers/patient.handler'
import { handleActionSave,} from '~/handlers/control-letter.handler'
import { toast } from '~/components/pub/ui/toast'
import Confirmation from '~/components/pub/my-ui/confirmation/confirmation.vue'
import { type ControlLetter } from '~/models/control-letter'
// #region Props & Emits
const props = defineProps<{
@@ -34,12 +15,16 @@ const props = defineProps<{
}>()
// 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
// #region State & Computed
const router = useRouter()
const isConfirmationOpen = ref(false)
const selectedSpecialistId = ref<number|null>(null)
const selectedDpjpId = ref<number|null>(null)
// #endregion
// #region Lifecycle Hooks
@@ -51,12 +36,42 @@ function goBack() {
}
async function handleConfirmAdd() {
// handleActionClick('submit')
console.log(`tersubmit wak`)
const controlLetter: ControlLetter = await composeFormData()
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() {
isConfirmationOpen.value = false
async function composeFormData(): Promise<ControlLetter> {
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
@@ -64,29 +79,6 @@ function handleCancelAdd() {
async function handleActionClick(eventType: string) {
if (eventType === 'submit') {
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') {
@@ -96,9 +88,15 @@ async function handleActionClick(eventType: string) {
}
goBack()
// handleCancelForm()
}
}
function handleCancelAdd() {
isConfirmationOpen.value = false
}
provide("selectedSpecialistId", selectedSpecialistId);
provide("selectedDpjpId", selectedDpjpId);
// #endregion
// #region Watchers
@@ -108,22 +106,19 @@ async function handleActionClick(eventType: string) {
<template>
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg font-semibold xl:text-xl">Tambah Surat Kontrol</div>
<AppOutpatientEncounterEntryForm
ref="personPatientForm"
:schema="ControlLetterSchema"
/>
ref="controlLetterForm"
:schema="ControlLetterSchema" />
<div class="my-2 flex justify-end py-2">
<Action :enable-draft="false" @click="handleActionClick" />
</div>
<Confirmation
v-model:open="isConfirmationOpen"
<Confirmation v-model:open="isConfirmationOpen"
title="Simpan Data"
message="Apakah Anda yakin ingin menyimpan data ini?"
confirm-text="Simpan"
@confirm="handleConfirmAdd"
@cancel="handleCancelAdd"
/>
@cancel="handleCancelAdd" />
</template>
<style scoped>
@@ -4,12 +4,11 @@ import { withBase } from '~/models/_base'
import type { HeaderPrep } from '~/components/pub/my-ui/data/types'
import type { Patient } from '~/models/patient'
import type { Person } from '~/models/person'
import { getDetail } from '~/services/control-letter.service'
// Components
import Header from '~/components/pub/my-ui/nav-header/prep.vue'
import { getPatientDetail } from '~/services/patient.service'
// #region Props & Emits
const props = defineProps<{
patientId: number
@@ -12,6 +12,7 @@ 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 { getDetail, update } from '~/services/control-letter.service'
import {
// for form entry
@@ -1,24 +1,23 @@
<script setup lang="ts">
import type { HeaderPrep, RefSearchNav } from '~/components/pub/my-ui/data/types'
import type { Summary } from '~/components/pub/my-ui/summary-card/type'
// #region Imports
import { Calendar, Hospital, UserCheck, UsersRound } from 'lucide-vue-next'
import RecordConfirmation from '~/components/pub/my-ui/confirmation/record-confirmation.vue'
import { ActionEvents } from '~/components/pub/my-ui/data/types'
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 { getPatients, removePatient } from '~/services/patient.service'
import { getList, remove } from '~/services/control-letter.service'
import { toast } from '~/components/pub/ui/toast'
// #endregion
// #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({
fetchFn: (params) => getPatients({ ...params, includes: ['person', 'person-Addresses'] }),
entityName: 'patient',
fetchFn: (params) => getList({ ...params, includes: 'specialist,subspecialist,doctor-employee-person', }),
entityName: 'control-letter',
})
const refSearchNav: RefSearchNav = {
@@ -45,24 +44,27 @@ const headerPrep: HeaderPrep = {
icon: 'i-lucide-newspaper',
addNav: {
label: "Surat Kontrol",
onClick: () => navigateTo('/outpatient/encounter/add'),
onClick: () => navigateTo({
name: 'rehab-encounter-id-control-letter-add',
params: { id: encounterId },
}),
},
}
// #endregion
// #region Lifecycle Hooks
onMounted(() => {
getPatientSummary()
getListData()
})
// #endregion
// #region Functions
async function getPatientSummary() {
async function getListData() {
try {
summaryLoading.value = true
await new Promise((resolve) => setTimeout(resolve, 500))
} catch (error) {
console.error('Error fetching patient summary:', error)
console.error('Error fetching Data:', error)
} finally {
summaryLoading.value = false
}
@@ -70,22 +72,17 @@ async function getPatientSummary() {
// Handle confirmation result
async function handleConfirmDelete(record: any, action: string) {
console.log('Confirmed action:', action, 'for record:', record)
if (action === 'delete' && record?.id) {
try {
const result = await removePatient(record.id)
const result = await remove(record.id)
if (result.success) {
console.log('Patient deleted successfully')
// Refresh the list
toast({ title: 'Berhasil', description: 'Data berhasil dihapus', variant: 'default' })
await fetchData()
} else {
console.error('Failed to delete patient:', result)
// Handle error - show error message to user
toast({ title: 'Gagal', description: `Data gagal dihapus`, variant: 'destructive' })
}
} catch (error) {
console.error('Error deleting patient:', error)
// Handle error - show error message to user
toast({ title: 'Gagal', description: `Something went wrong`, variant: 'destructive' })
}
}
}
@@ -134,28 +131,10 @@ watch([recId, recAction], () => {
</script>
<template>
<Header v-model:search="searchInput" :prep="{ ...headerPrep }" :ref-search-nav="refSearchNav" />
<!-- Disable dulu, ayahab kalo diminta beneran -->
<!-- <div class="my-4 flex flex-1 flex-col gap-4 md:gap-8">
<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>
-->
<Header v-model:search="searchInput"
:prep="{ ...headerPrep }"
:ref-search-nav="refSearchNav"
@search="handleSearch" />
<AppOutpatientEncounterList :data="data" :pagination-meta="paginationMeta" @page-change="handlePageChange" />