From b2a512314b397f73ba9f3078960831dd99818526 Mon Sep 17 00:00:00 2001 From: hasyim_kai Date: Fri, 7 Nov 2025 14:02:54 +0700 Subject: [PATCH 1/9] Feat: UI uplaod doc pendukung --- .../_common/select-doc-type.vue | 81 ++++++++++++ .../app/document-upload/entry-form.vue | 77 +++++++++++ .../app/document-upload/list.cfg.ts | 39 ++++++ app/components/app/document-upload/list.vue | 31 +++++ .../content/document-upload/add.vue | 122 +++++++++++++++++ .../content/document-upload/edit.vue | 122 +++++++++++++++++ .../content/document-upload/list.vue | 123 ++++++++++++++++++ app/components/content/encounter/process.vue | 3 +- .../pub/my-ui/nav-footer/ba-dr-su.vue | 6 +- .../document-upload/[document_id]/edit.vue | 41 ++++++ .../encounter/[id]/document-upload/add.vue | 42 ++++++ app/schemas/document-upload.schema.ts | 13 ++ app/services/control-letter.service.ts | 28 ++++ 13 files changed, 725 insertions(+), 3 deletions(-) create mode 100644 app/components/app/document-upload/_common/select-doc-type.vue create mode 100644 app/components/app/document-upload/entry-form.vue create mode 100644 app/components/app/document-upload/list.cfg.ts create mode 100644 app/components/app/document-upload/list.vue create mode 100644 app/components/content/document-upload/add.vue create mode 100644 app/components/content/document-upload/edit.vue create mode 100644 app/components/content/document-upload/list.vue create mode 100644 app/pages/(features)/rehab/encounter/[id]/document-upload/[document_id]/edit.vue create mode 100644 app/pages/(features)/rehab/encounter/[id]/document-upload/add.vue create mode 100644 app/schemas/document-upload.schema.ts create mode 100644 app/services/control-letter.service.ts diff --git a/app/components/app/document-upload/_common/select-doc-type.vue b/app/components/app/document-upload/_common/select-doc-type.vue new file mode 100644 index 00000000..26b1bb87 --- /dev/null +++ b/app/components/app/document-upload/_common/select-doc-type.vue @@ -0,0 +1,81 @@ + + + diff --git a/app/components/app/document-upload/entry-form.vue b/app/components/app/document-upload/entry-form.vue new file mode 100644 index 00000000..7197c02b --- /dev/null +++ b/app/components/app/document-upload/entry-form.vue @@ -0,0 +1,77 @@ + + + diff --git a/app/components/app/document-upload/list.cfg.ts b/app/components/app/document-upload/list.cfg.ts new file mode 100644 index 00000000..dbe2a679 --- /dev/null +++ b/app/components/app/document-upload/list.cfg.ts @@ -0,0 +1,39 @@ +import type { Config } from '~/components/pub/my-ui/data-table' +import { defineAsyncComponent } from 'vue' + +const action = defineAsyncComponent(() => import('~/components/pub/my-ui/data/dropdown-action-dud.vue')) + +export const config: Config = { + cols: [{}, {}, {}, {width: 50},], + + headers: [ + [ + { label: 'Nama Dokumen' }, + { label: 'Tipe Dokumen' }, + { label: 'Petugas Upload' }, + { label: 'Action' }, + ], + ], + + keys: ['specialist.name', 'subspecialist.name', 'subspecialist.name', 'action'], + + delKeyNames: [ + + ], + + parses: { + }, + + components: { + action(rec, idx) { + return { + idx, + rec: rec as object, + component: action, + } + }, + }, + + htmls: { + }, +} diff --git a/app/components/app/document-upload/list.vue b/app/components/app/document-upload/list.vue new file mode 100644 index 00000000..8274e752 --- /dev/null +++ b/app/components/app/document-upload/list.vue @@ -0,0 +1,31 @@ + + + diff --git a/app/components/content/document-upload/add.vue b/app/components/content/document-upload/add.vue new file mode 100644 index 00000000..6fbc43f5 --- /dev/null +++ b/app/components/content/document-upload/add.vue @@ -0,0 +1,122 @@ + + + + + diff --git a/app/components/content/document-upload/edit.vue b/app/components/content/document-upload/edit.vue new file mode 100644 index 00000000..63f1fc09 --- /dev/null +++ b/app/components/content/document-upload/edit.vue @@ -0,0 +1,122 @@ + + + + + diff --git a/app/components/content/document-upload/list.vue b/app/components/content/document-upload/list.vue new file mode 100644 index 00000000..c69ac96b --- /dev/null +++ b/app/components/content/document-upload/list.vue @@ -0,0 +1,123 @@ + + + diff --git a/app/components/content/encounter/process.vue b/app/components/content/encounter/process.vue index ad93387d..7d178303 100644 --- a/app/components/content/encounter/process.vue +++ b/app/components/content/encounter/process.vue @@ -16,6 +16,7 @@ import EarlyMedicalAssesmentList from '~/components/content/soapi/entry.vue' import EarlyMedicalRehabList from '~/components/content/soapi/entry.vue' import PrescriptionList from '~/components/content/prescription/list.vue' import Consultation from '~/components/content/consultation/list.vue' +import DocUploadList from '~/components/content/document-upload/list.vue' const route = useRoute() const router = useRouter() @@ -72,7 +73,7 @@ const tabs: TabItem[] = [ { value: 'resume', label: 'Resume' }, { value: 'control', label: 'Surat Kontrol' }, { value: 'screening', label: 'Skrinning MPP' }, - { value: 'supporting-document', label: 'Upload Dokumen Pendukung' }, + { value: 'supporting-document', label: 'Upload Dokumen Pendukung', component: DocUploadList, props: { encounter: data } }, ] diff --git a/app/components/pub/my-ui/nav-footer/ba-dr-su.vue b/app/components/pub/my-ui/nav-footer/ba-dr-su.vue index 4598817b..427eab0f 100644 --- a/app/components/pub/my-ui/nav-footer/ba-dr-su.vue +++ b/app/components/pub/my-ui/nav-footer/ba-dr-su.vue @@ -1,10 +1,12 @@ + + diff --git a/app/pages/(features)/rehab/encounter/[id]/document-upload/add.vue b/app/pages/(features)/rehab/encounter/[id]/document-upload/add.vue new file mode 100644 index 00000000..94805d12 --- /dev/null +++ b/app/pages/(features)/rehab/encounter/[id]/document-upload/add.vue @@ -0,0 +1,42 @@ + + + diff --git a/app/schemas/document-upload.schema.ts b/app/schemas/document-upload.schema.ts new file mode 100644 index 00000000..ffd56e36 --- /dev/null +++ b/app/schemas/document-upload.schema.ts @@ -0,0 +1,13 @@ +import { z } from 'zod' + +const DocumentUploadSchema = z.object({ + officer: z.string({ required_error: 'Mohon isi', }), + doc_name: z.number({ required_error: 'Mohon isi', }), + doc_type: z.number({ required_error: 'Mohon isi', }), + file: z.number({ required_error: 'Mohon isi', }), +}) + +type DocumentUploadFormData = z.infer + +export { DocumentUploadSchema } +export type { DocumentUploadFormData } diff --git a/app/services/control-letter.service.ts b/app/services/control-letter.service.ts new file mode 100644 index 00000000..29b3722b --- /dev/null +++ b/app/services/control-letter.service.ts @@ -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) +} \ No newline at end of file From 56109564cb3c473172a86de167b7cce816a36973 Mon Sep 17 00:00:00 2001 From: hasyim_kai Date: Thu, 13 Nov 2025 16:16:26 +0700 Subject: [PATCH 2/9] Feat: API Integration supporting doc upload --- .../_common/select-doc-type.vue | 14 +--- .../app/document-upload/entry-form.vue | 10 +-- .../app/document-upload/list.cfg.ts | 4 +- app/components/app/document-upload/list.vue | 6 +- .../content/document-upload/add.vue | 46 ++++++----- .../content/document-upload/edit.vue | 54 ++++++++----- .../content/document-upload/list.vue | 27 ++++--- app/components/content/encounter/process.vue | 19 +++-- .../pub/my-ui/data/dropdown-action-dd.vue | 80 +++++++++++++++++++ app/components/pub/my-ui/form/file-field.vue | 2 +- app/handlers/supporting-document.handler.ts | 24 ++++++ app/lib/constants.ts | 38 +++++++++ app/lib/utils.ts | 48 +++++++++++ app/models/encounter-document.ts | 29 +++++++ app/models/encounter.ts | 5 +- .../rehab/encounter/[id]/process.vue | 14 ++-- .../(features)/rehab/encounter/index.vue | 8 +- app/schemas/document-upload.schema.ts | 19 ++++- app/services/supporting-document.service.ts | 55 +++++++++++++ 19 files changed, 401 insertions(+), 101 deletions(-) create mode 100644 app/components/pub/my-ui/data/dropdown-action-dd.vue create mode 100644 app/handlers/supporting-document.handler.ts create mode 100644 app/models/encounter-document.ts create mode 100644 app/services/supporting-document.service.ts diff --git a/app/components/app/document-upload/_common/select-doc-type.vue b/app/components/app/document-upload/_common/select-doc-type.vue index 26b1bb87..0e86f596 100644 --- a/app/components/app/document-upload/_common/select-doc-type.vue +++ b/app/components/app/document-upload/_common/select-doc-type.vue @@ -2,7 +2,7 @@ import type { FormErrors } from '~/types/error' import Combobox from '~/components/pub/my-ui/combobox/combobox.vue' import { cn, mapToComboboxOptList } from '~/lib/utils' -import { occupationCodes } from '~/lib/constants' +import { supportingDocTypeCode, supportingDocOpt, type supportingDocTypeCodeKey } from '~/lib/constants' import { getValueLabelList as getDoctorLabelList } from '~/services/doctor.service' import { getValueLabelList as getUnitLabelList } from '~/services/unit.service' @@ -31,16 +31,6 @@ const { labelClass, } = props -const docTypeOpts : Item[] = [ - { - label: 'Surat Keterangan Sehat', - value: 'sksehat', - }, - { - label: 'Surat Keterangan Sakit', - value: 'sksakit', - }, -] diff --git a/app/components/content/document-upload/add.vue b/app/components/content/document-upload/add.vue index 6fbc43f5..5ee3d862 100644 --- a/app/components/content/document-upload/add.vue +++ b/app/components/content/document-upload/add.vue @@ -2,10 +2,12 @@ import { useRouter } from 'vue-router' import type { ExposedForm } from '~/types/form' import Action from '~/components/pub/my-ui/nav-footer/ba-dr-su.vue' -import { handleActionSave,} from '~/handlers/patient.handler' +import { handleActionSave,} from '~/handlers/supporting-document.handler' import { toast } from '~/components/pub/ui/toast' import Confirmation from '~/components/pub/my-ui/confirmation/confirmation.vue' import { DocumentUploadSchema } from '~/schemas/document-upload.schema' +import { uploadAttachment } from '~/services/supporting-document.service' +import { printFormData, toFormData } from '~/lib/utils' // #region Props & Emits const props = defineProps<{ @@ -16,11 +18,15 @@ const props = defineProps<{ const route = useRoute() const encounterId = typeof route.params.id == 'string' ? parseInt(route.params.id) : 0 const inputForm = ref | null>(null) +const { user } = useUserStore() // #endregion // #region State & Computed const router = useRouter() const isConfirmationOpen = ref(false) +const initialValues = { + officer: user.user_name, +} // #endregion // #region Lifecycle Hooks @@ -31,42 +37,42 @@ function goBack() { router.go(-1) } + async function handleConfirmAdd() { - const controlLetter = await composeFormData() - let createdControlLetterId = 0 - - const response = await handleActionSave( - controlLetter, - () => { }, - () => { }, - toast, - ) + const inputData = await composeFormData() + const inputFormData: FormData = toFormData(inputData) + const response = await handleActionSave(inputFormData, () => { }, () => { }, 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) + navigateTo(props.callbackUrl + '?control-letter-id=' + inputData.id) } - goBack() } async function composeFormData(): Promise { - const [controlLetter,] = await Promise.all([ + inputForm.value?.setValues({ + ...inputForm.value?.values, + ref_id: encounterId, + upload_employee_id: user.user_id + }) + + const [inputFormState,] = await Promise.all([ inputForm.value?.validate(), ]) - const results = [controlLetter] + const results = [inputFormState] 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 + if (!allValid) { + toast({ title: 'Form validation failed', variant: 'destructive',}) + return Promise.reject('Form validation failed') + } + const formData = inputFormState?.values return new Promise((resolve) => resolve(formData)) } // #endregion region @@ -82,7 +88,6 @@ async function handleActionClick(eventType: string) { await navigateTo(props.callbackUrl) return } - goBack() } } @@ -103,6 +108,7 @@ function handleCancelAdd() {
diff --git a/app/components/content/document-upload/edit.vue b/app/components/content/document-upload/edit.vue index 63f1fc09..c4033fb2 100644 --- a/app/components/content/document-upload/edit.vue +++ b/app/components/content/document-upload/edit.vue @@ -2,10 +2,11 @@ import { useRouter } from 'vue-router' import type { ExposedForm } from '~/types/form' import Action from '~/components/pub/my-ui/nav-footer/ba-dr-su.vue' -import { handleActionSave,} from '~/handlers/patient.handler' +import { handleActionSave,} from '~/handlers/supporting-document.handler' import { toast } from '~/components/pub/ui/toast' import Confirmation from '~/components/pub/my-ui/confirmation/confirmation.vue' import { DocumentUploadSchema } from '~/schemas/document-upload.schema' +import { getDetail } from '~/services/supporting-document.service' // #region Props & Emits const props = defineProps<{ @@ -15,15 +16,26 @@ const props = defineProps<{ // form related state const route = useRoute() const encounterId = typeof route.params.id == 'string' ? parseInt(route.params.id) : 0 +const docId = typeof route.params.document_id == 'string' ? parseInt(route.params.document_id) : 0 const inputForm = ref | null>(null) // #endregion // #region State & Computed const router = useRouter() const isConfirmationOpen = ref(false) +const { user } = useUserStore() +const initialValues = { + officer: user.user_name, +} // #endregion // #region Lifecycle Hooks +onMounted(async () => { + const result = await getDetail(docId) + if (result.success) { + inputForm.value?.setValues(result.body.data) + } +}) // #endregion // #region Functions @@ -32,40 +44,39 @@ function goBack() { } async function handleConfirmAdd() { - const controlLetter = await composeFormData() - let createdControlLetterId = 0 + const inputData = await composeFormData() + let createdDataId = 0 - const response = await handleActionSave( - controlLetter, - () => { }, - () => { }, - toast, - ) + // const response = await handleActionSave( + // inputData, + // () => { }, + // () => { }, + // toast, + // ) - const data = (response?.body?.data ?? null) - if (!data) return - createdControlLetterId = data.id + // const data = (response?.body?.data ?? null) + // if (!data) return + // createdDataId = data.id - // // If has callback provided redirect to callback with patientData - if (props.callbackUrl) { - navigateTo(props.callbackUrl + '?control-letter-id=' + controlLetter.id) - } - - goBack() + // // // If has callback provided redirect to callback with patientData + // if (props.callbackUrl) { + // navigateTo(props.callbackUrl + '?control-letter-id=' + inputData.id) + // } + // goBack() } async function composeFormData(): Promise { - const [controlLetter,] = await Promise.all([ + const [inputFormState,] = await Promise.all([ inputForm.value?.validate(), ]) - const results = [controlLetter] + const results = [inputFormState] 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 + const formData = inputFormState?.values formData.encounter_id = encounterId return new Promise((resolve) => resolve(formData)) } @@ -103,6 +114,7 @@ function handleCancelAdd() {
diff --git a/app/components/content/document-upload/list.vue b/app/components/content/document-upload/list.vue index c69ac96b..94f9dd9f 100644 --- a/app/components/content/document-upload/list.vue +++ b/app/components/content/document-upload/list.vue @@ -4,28 +4,31 @@ import { ActionEvents } from '~/components/pub/my-ui/data/types' import type { HeaderPrep, } from '~/components/pub/my-ui/data/types' import Header from '~/components/pub/my-ui/nav-header/prep.vue' import { usePaginatedList } from '~/composables/usePaginatedList' -import { getList, remove } from '~/services/control-letter.service' +import { getList, remove } from '~/services/supporting-document.service' import { toast } from '~/components/pub/ui/toast' import type { Encounter } from '~/models/encounter' import RecordConfirmation from '~/components/pub/my-ui/confirmation/record-confirmation.vue' +import { genEncounterDocument } from '~/models/encounter-document' // #endregion // #region State const props = defineProps<{ encounter?: Encounter + refresh: () => void }>() + 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) => getList({ ...params, includes: 'specialist,subspecialist,doctor-employee-person', }), - entityName: 'control-letter', +const dummy = computed(() => { + return props.encounter?.encounterDocuments || [] }) const isRecordConfirmationOpen = ref(false) const recId = ref(0) const recAction = ref('') const recItem = ref(null) +const timestamp = ref(0) const headerPrep: HeaderPrep = { title: "Upload Dokumen", @@ -53,7 +56,7 @@ async function handleConfirmDelete(record: any, action: string) { const result = await remove(record.id) if (result.success) { toast({ title: 'Berhasil', description: 'Data berhasil dihapus', variant: 'default' }) - await fetchData() + props.refresh() } else { toast({ title: 'Gagal', description: `Data gagal dihapus`, variant: 'destructive' }) } @@ -63,6 +66,7 @@ async function handleConfirmDelete(record: any, action: string) { } } + function handleCancelConfirmation() { // Reset record state when cancelled recId.value = 0 @@ -75,13 +79,14 @@ function handleCancelConfirmation() { provide('rec_id', recId) provide('rec_action', recAction) provide('rec_item', recItem) +provide('timestamp', timestamp) // #endregion // #region Watchers -watch([recId, recAction], () => { +watch([recId, recAction, timestamp], () => { switch (recAction.value) { case ActionEvents.showDetail: - navigateTo("https://google.com", { external: true, open: { target: '_blank' } }) + navigateTo(recItem.value.filePath, { external: true, open: { target: '_blank' } }) break case ActionEvents.showEdit: navigateTo({ @@ -99,7 +104,7 @@ watch([recId, recAction], () => { diff --git a/app/components/content/encounter/process.vue b/app/components/content/encounter/process.vue index 7d178303..1f9a37a1 100644 --- a/app/components/content/encounter/process.vue +++ b/app/components/content/encounter/process.vue @@ -17,6 +17,7 @@ import EarlyMedicalRehabList from '~/components/content/soapi/entry.vue' import PrescriptionList from '~/components/content/prescription/list.vue' import Consultation from '~/components/content/consultation/list.vue' import DocUploadList from '~/components/content/document-upload/list.vue' +import { genEncounter } from '~/models/encounter' const route = useRoute() const router = useRouter() @@ -30,12 +31,18 @@ const activeTab = computed({ }) const id = typeof route.params.id == 'string' ? parseInt(route.params.id) : 0 -const dataRes = await getDetail(id, { - includes: - 'patient,patient-person,patient-person-addresses,unit,Appointment_Doctor,Appointment_Doctor-employee,Appointment_Doctor-employee-person', +const data = ref(genEncounter()) + +async function fetchDetail() { + const res = await getDetail(id, { + includes: 'patient,patient-person,patient-person-addresses,unit,Appointment_Doctor,Appointment_Doctor-employee,Appointment_Doctor-employee-person,EncounterDocuments', + }) + if(res.body?.data) data.value = res.body?.data +} + +onMounted(() => { + fetchDetail() }) -const dataResBody = dataRes.body ?? null -const data = dataResBody?.data ?? null const tabs: TabItem[] = [ { value: 'status', label: 'Status Masuk/Keluar', component: Status, props: { encounter: data } }, @@ -73,7 +80,7 @@ const tabs: TabItem[] = [ { value: 'resume', label: 'Resume' }, { value: 'control', label: 'Surat Kontrol' }, { value: 'screening', label: 'Skrinning MPP' }, - { value: 'supporting-document', label: 'Upload Dokumen Pendukung', component: DocUploadList, props: { encounter: data } }, + { value: 'supporting-document', label: 'Upload Dokumen Pendukung', component: DocUploadList, props: { encounter: data, }, }, ] diff --git a/app/components/pub/my-ui/data/dropdown-action-dd.vue b/app/components/pub/my-ui/data/dropdown-action-dd.vue new file mode 100644 index 00000000..a6a99c9a --- /dev/null +++ b/app/components/pub/my-ui/data/dropdown-action-dd.vue @@ -0,0 +1,80 @@ + + + diff --git a/app/components/pub/my-ui/form/file-field.vue b/app/components/pub/my-ui/form/file-field.vue index bc6a86c9..31885a6f 100644 --- a/app/components/pub/my-ui/form/file-field.vue +++ b/app/components/pub/my-ui/form/file-field.vue @@ -62,7 +62,7 @@ async function onFileChange(event: Event, handleChange: (value: any) => void) { @change="onFileChange($event, handleChange)" type="file" :disabled="isDisabled" - v-bind="componentField" + v-bind="{ onBlur: componentField.onBlur }" :placeholder="placeholder" :class="cn('focus:border-primary focus:ring-2 focus:ring-primary focus:ring-offset-0')" /> diff --git a/app/handlers/supporting-document.handler.ts b/app/handlers/supporting-document.handler.ts new file mode 100644 index 00000000..70b29612 --- /dev/null +++ b/app/handlers/supporting-document.handler.ts @@ -0,0 +1,24 @@ +// Handlers +import { genCrudHandler } from '~/handlers/_handler' + +// Services +import { create, update, remove } from '~/services/supporting-document.service' + +export const { + recId, + recAction, + recItem, + isReadonly, + isProcessing, + isFormEntryDialogOpen, + isRecordConfirmationOpen, + onResetState, + handleActionSave, + handleActionEdit, + handleActionRemove, + handleCancelForm, +} = genCrudHandler({ + create, + update, + remove, +}) diff --git a/app/lib/constants.ts b/app/lib/constants.ts index 3a52b22e..f684594e 100644 --- a/app/lib/constants.ts +++ b/app/lib/constants.ts @@ -383,3 +383,41 @@ export const medicalActionTypeCode: Record = { } as const export type medicalActionTypeCodeKey = keyof typeof medicalActionTypeCode + +export const encounterDocTypeCode: Record = { + "person-resident-number": 'person-resident-number', + "person-driving-license": 'person-driving-license', + "person-passport": 'person-passport', + "person-family-card": 'person-family-card', + "mcu-item-result": 'mcu-item-result', + "vclaim-sep": 'vclaim-sep', + "vclaim-sipp": 'vclaim-sipp', +} as const +export type encounterDocTypeCodeKey = keyof typeof encounterDocTypeCode +export const encounterDocOpt: { label: string; value: encounterDocTypeCodeKey }[] = [ + { label: 'KTP', value: 'person-resident-number' }, + { label: 'SIM', value: 'person-driving-license' }, + { label: 'Passport', value: 'person-passport' }, + { label: 'Kartu Keluarga', value: 'person-family-card' }, + { label: 'Hasil MCU', value: 'mcu-item-result' }, + { label: 'Klaim SEP', value: 'vclaim-sep' }, + { label: 'Klaim SIPP', value: 'vclaim-sipp' }, +] + + +export const supportingDocTypeCode = { + "encounter-patient": 'encounter-patient', + "encounter-suport": 'encounter-suport', + "encounter-other": 'encounter-other', +} as const +export const supportingDocTypeLabel = { + "encounter-patient": 'Data Pasien', + "encounter-suport": 'Data Penunjang', + "encounter-other": 'Lain - Lain', +} as const +export type supportingDocTypeCodeKey = keyof typeof supportingDocTypeCode +export const supportingDocOpt = [ + { label: 'Data Pasien', value: 'encounter-patient' }, + { label: 'Data Penunjang', value: 'encounter-suport' }, + { label: 'Lain - Lain', value: 'encounter-other' }, +] diff --git a/app/lib/utils.ts b/app/lib/utils.ts index 357d8700..9c0aada8 100644 --- a/app/lib/utils.ts +++ b/app/lib/utils.ts @@ -104,3 +104,51 @@ export function calculateAge(birthDate: Date | string | null | undefined): strin return `${years} tahun ${months} bulan` } } + + +/** + * Converts a plain JavaScript object (including File objects) into a FormData instance. + * @param {object} data - The object to convert (e.g., form values). + * @returns {FormData} The new FormData object suitable for API submission. + */ +export function toFormData(data: Record): FormData { + const formData = new FormData(); + + for (const key in data) { + if (Object.prototype.hasOwnProperty.call(data, key)) { + const value = data[key]; + + // Handle File objects, Blobs, or standard JSON values + if (value !== null && value !== undefined) { + // Check if the value is a File/Blob instance + if (value instanceof File || value instanceof Blob) { + // Append the file directly + formData.append(key, value); + } else if (typeof value === 'object') { + // Handle nested objects/arrays by stringifying them (optional, depends on API) + // Note: Most APIs expect nested data to be handled separately or passed as JSON string + // For simplicity, we stringify non-File objects. + formData.append(key, JSON.stringify(value)); + } else { + // Append standard string, number, or boolean values + formData.append(key, value); + } + } + } + } + + return formData; +} + +export function printFormData(formData: FormData) { + console.log("--- FormData Contents ---"); + // Use the entries() iterator to loop through key/value pairs + for (const [key, value] of formData.entries()) { + if (value instanceof File) { + console.log(`Key: ${key}, Value: [File: ${value.name}, Type: ${value.type}, Size: ${value.size} bytes]`); + } else { + console.log(`Key: ${key}, Value: "${value}"`); + } + } + console.log("-------------------------"); +} \ No newline at end of file diff --git a/app/models/encounter-document.ts b/app/models/encounter-document.ts new file mode 100644 index 00000000..c4f9898c --- /dev/null +++ b/app/models/encounter-document.ts @@ -0,0 +1,29 @@ +import { type Base, genBase } from "./_base" +import { supportingDocOpt, supportingDocTypeCode, supportingDocTypeLabel, type supportingDocTypeCodeKey } from '~/lib/constants' +import { genEmployee, type Employee } from "./employee" +import { genEncounter, type Encounter } from "./encounter" + +export interface EncounterDocument extends Base { + encounter_id: number + encounter?: Encounter + upload_employee_id: number + employee?: Employee + type_code: string + name: string + filePath: string + fileName: string +} + +export function genEncounterDocument(): EncounterDocument { + return { + ...genBase(), + encounter_id: 2, + encounter: genEncounter(), + upload_employee_id: 0, + employee: genEmployee(), + type_code: supportingDocTypeLabel["encounter-patient"], + name: 'example', + filePath: 'https://bing.com', + fileName: 'example', + } +} diff --git a/app/models/encounter.ts b/app/models/encounter.ts index fb2c0b04..55fbdfa4 100644 --- a/app/models/encounter.ts +++ b/app/models/encounter.ts @@ -1,6 +1,7 @@ import type { DeathCause } from "./death-cause" import { type Doctor, genDoctor } from "./doctor" import { genEmployee, type Employee } from "./employee" +import type { EncounterDocument } from "./encounter-document" import type { InternalReference } from "./internal-reference" import { type Patient, genPatient } from "./patient" import type { Specialist } from "./specialist" @@ -37,6 +38,7 @@ export interface Encounter { internalReferences?: InternalReference[] deathCause?: DeathCause status_code: string + encounterDocuments: EncounterDocument[] } export function genEncounter(): Encounter { @@ -54,7 +56,8 @@ export function genEncounter(): Encounter { appointment_doctor_id: 0, appointment_doctor: genDoctor(), medicalDischargeEducation: '', - status_code: '' + status_code: '', + encounterDocuments: [], } } diff --git a/app/pages/(features)/rehab/encounter/[id]/process.vue b/app/pages/(features)/rehab/encounter/[id]/process.vue index 3fa7525a..abd0efa7 100644 --- a/app/pages/(features)/rehab/encounter/[id]/process.vue +++ b/app/pages/(features)/rehab/encounter/[id]/process.vue @@ -22,15 +22,15 @@ const { checkRole, hasCreateAccess } = useRBAC() // Check if user has access to this page const hasAccess = checkRole(roleAccess) -if (!hasAccess) { - throw createError({ - statusCode: 403, - statusMessage: 'Access denied', - }) -} +// if (!hasAccess) { +// throw createError({ +// statusCode: 403, +// statusMessage: 'Access denied', +// }) +// } // Define permission-based computed properties -const canCreate = hasCreateAccess(roleAccess) +const canCreate = true // hasCreateAccess(roleAccess)