From b2a512314b397f73ba9f3078960831dd99818526 Mon Sep 17 00:00:00 2001
From: hasyim_kai
Date: Fri, 7 Nov 2025 14:02:54 +0700
Subject: [PATCH 1/6] 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 @@
+
+
+
+
+
+ {{ label }}
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
Upload Dokumen
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
Upload Dokumen
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+
+ ID:
+ {{ record?.id }}
+
+
+ Nama:
+ {{ record.firstName }}
+
+
+ Kode:
+ {{ record.cellphone }}
+
+
+
+
+
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/6] 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',
- },
-]
@@ -67,7 +57,7 @@ const docTypeOpts : Item[] = [
class="focus:ring-0 focus:ring-offset-0"
:id="fieldName"
v-bind="componentField"
- :items="docTypeOpts"
+ :items="supportingDocOpt"
:placeholder="placeholder"
search-placeholder="Cari..."
empty-message="Data tidak ditemukan"
diff --git a/app/components/app/document-upload/entry-form.vue b/app/components/app/document-upload/entry-form.vue
index 7197c02b..f97a5161 100644
--- a/app/components/app/document-upload/entry-form.vue
+++ b/app/components/app/document-upload/entry-form.vue
@@ -12,10 +12,6 @@ const props = defineProps<{
schema: any
initialValues?: any
errors?: FormErrors
-
- selectedUnitId?: number | null
- selectedSpecialistId?: number | null
- selectedSubSpecialistId?: number | null
}>()
const formSchema = toTypedSchema(props.schema)
@@ -51,19 +47,19 @@ defineExpose({
import('~/components/pub/my-ui/data/dropdown-action-dud.vue'))
+const action = defineAsyncComponent(() => import('~/components/pub/my-ui/data/dropdown-action-dd.vue'))
export const config: Config = {
cols: [{}, {}, {}, {width: 50},],
@@ -15,7 +15,7 @@ export const config: Config = {
],
],
- keys: ['specialist.name', 'subspecialist.name', 'subspecialist.name', 'action'],
+ keys: ['fileName', 'type_code', 'employee.name', 'action'],
delKeyNames: [
diff --git a/app/components/app/document-upload/list.vue b/app/components/app/document-upload/list.vue
index 8274e752..dde32820 100644
--- a/app/components/app/document-upload/list.vue
+++ b/app/components/app/document-upload/list.vue
@@ -5,7 +5,7 @@ import { config } from './list.cfg'
interface Props {
data: any[]
- paginationMeta: PaginationMeta
+ // paginationMeta: PaginationMeta
}
defineProps()
@@ -24,8 +24,8 @@ function handlePageChange(page: number) {
-
+
+
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], () => {
-
+
@@ -111,11 +116,7 @@ watch([recId, recAction], () => {
Nama:
- {{ record.firstName }}
-
-
- Kode:
- {{ record.cellphone }}
+ {{ record.name }}
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)
diff --git a/app/pages/(features)/rehab/encounter/index.vue b/app/pages/(features)/rehab/encounter/index.vue
index 7a8564a8..9deaeb1f 100644
--- a/app/pages/(features)/rehab/encounter/index.vue
+++ b/app/pages/(features)/rehab/encounter/index.vue
@@ -22,12 +22,12 @@ const { checkRole, hasReadAccess } = useRBAC()
// Check if user has access to this page
const hasAccess = checkRole(roleAccess)
-if (!hasAccess) {
- navigateTo('/403')
-}
+// if (!hasAccess) {
+// navigateTo('/403')
+// }
// Define permission-based computed properties
-const canRead = hasReadAccess(roleAccess)
+const canRead = true // hasReadAccess(roleAccess)
diff --git a/app/schemas/document-upload.schema.ts b/app/schemas/document-upload.schema.ts
index ffd56e36..7f7de622 100644
--- a/app/schemas/document-upload.schema.ts
+++ b/app/schemas/document-upload.schema.ts
@@ -1,10 +1,21 @@
import { z } from 'zod'
+const ACCEPTED_UPLOAD_TYPES = ['image/jpeg', 'image/png', 'application/pdf']
+const MAX_SIZE_BYTES = 1 * 1024 * 1024 // 1MB
+
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', }),
+ entityType_code: z.string().default('encounter'),
+ ref_id: z.number(),
+ upload_employee_id: z.number(),
+ name: z.string({ required_error: 'Mohon isi', }),
+ type_code: z.string({ required_error: 'Mohon isi', }),
+ content: z.custom()
+ .refine((f) => f, { message: 'File tidak boleh kosong' })
+ .refine((f) => !f || f instanceof File, { message: 'Harus berupa file yang valid' })
+ .refine((f) => !f || ACCEPTED_UPLOAD_TYPES.includes(f.type), {
+ message: 'Format file harus JPG, PNG, atau PDF',
+ })
+ .refine((f) => !f || f.size <= MAX_SIZE_BYTES, { message: 'Maksimal 1MB' }),
})
type DocumentUploadFormData = z.infer
diff --git a/app/services/supporting-document.service.ts b/app/services/supporting-document.service.ts
new file mode 100644
index 00000000..b02cec0e
--- /dev/null
+++ b/app/services/supporting-document.service.ts
@@ -0,0 +1,55 @@
+// Base
+import * as base from './_crud-base'
+
+// Constants
+import { encounterClassCodes, uploadCode, type UploadCodeKey } from '~/lib/constants'
+
+const path = '/api/v1/upload'
+const name = 'upload'
+
+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)
+}
+
+export async function uploadAttachment(file: File, userId: number, key: UploadCodeKey) {
+ try {
+ const resolvedKey = uploadCode[key]
+ if (!resolvedKey) {
+ throw new Error(`Invalid upload code key: ${key}`)
+ }
+
+ // siapkan form-data body
+ const formData = new FormData()
+ formData.append('code', resolvedKey)
+ formData.append('content', file)
+
+ // kirim via xfetch
+ const resp = await xfetch(`${path}/${userId}/upload`, 'POST', formData)
+
+ // struktur hasil sama seperti patchPatient
+ const result: any = {}
+ result.success = resp.success
+ result.body = (resp.body as Record) || {}
+
+ return result
+ } catch (error) {
+ console.error('Error uploading attachment:', error)
+ throw new Error('Failed to upload attachment')
+ }
+}
\ No newline at end of file
From 1d03258f4403122fabf43148c739756191470971 Mon Sep 17 00:00:00 2001
From: hasyim_kai
Date: Fri, 14 Nov 2025 14:08:47 +0700
Subject: [PATCH 3/6] Fix: Typo uplaod doc type
---
app/lib/constants.ts | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/app/lib/constants.ts b/app/lib/constants.ts
index f684594e..430b2902 100644
--- a/app/lib/constants.ts
+++ b/app/lib/constants.ts
@@ -407,17 +407,17 @@ export const encounterDocOpt: { label: string; value: encounterDocTypeCodeKey }[
export const supportingDocTypeCode = {
"encounter-patient": 'encounter-patient',
- "encounter-suport": 'encounter-suport',
+ "encounter-support": 'encounter-support',
"encounter-other": 'encounter-other',
} as const
export const supportingDocTypeLabel = {
"encounter-patient": 'Data Pasien',
- "encounter-suport": 'Data Penunjang',
+ "encounter-support": '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: 'Data Penunjang', value: 'encounter-support' },
{ label: 'Lain - Lain', value: 'encounter-other' },
]
From 60c13649d9ccc0653136bfc41e70c3ab73278a80 Mon Sep 17 00:00:00 2001
From: hasyim_kai
Date: Fri, 14 Nov 2025 14:55:32 +0700
Subject: [PATCH 4/6] Fix: debug Uplaod Doc
---
app/components/app/document-upload/list.vue | 6 ++--
.../content/document-upload/add.vue | 2 +-
.../content/document-upload/list.vue | 35 +++++++++++++------
app/services/supporting-document.service.ts | 7 ++--
4 files changed, 33 insertions(+), 17 deletions(-)
diff --git a/app/components/app/document-upload/list.vue b/app/components/app/document-upload/list.vue
index dde32820..8274e752 100644
--- a/app/components/app/document-upload/list.vue
+++ b/app/components/app/document-upload/list.vue
@@ -5,7 +5,7 @@ import { config } from './list.cfg'
interface Props {
data: any[]
- // paginationMeta: PaginationMeta
+ paginationMeta: PaginationMeta
}
defineProps()
@@ -24,8 +24,8 @@ function handlePageChange(page: number) {
-
-
+
diff --git a/app/components/content/document-upload/add.vue b/app/components/content/document-upload/add.vue
index 5ee3d862..7d42f4f6 100644
--- a/app/components/content/document-upload/add.vue
+++ b/app/components/content/document-upload/add.vue
@@ -57,7 +57,7 @@ async function composeFormData(): Promise {
inputForm.value?.setValues({
...inputForm.value?.values,
ref_id: encounterId,
- upload_employee_id: user.user_id
+ upload_employee_id: user.employee_id
})
const [inputFormState,] = await Promise.all([
diff --git a/app/components/content/document-upload/list.vue b/app/components/content/document-upload/list.vue
index 94f9dd9f..be690871 100644
--- a/app/components/content/document-upload/list.vue
+++ b/app/components/content/document-upload/list.vue
@@ -1,14 +1,12 @@
-
-
+
+
@@ -114,9 +129,9 @@ watch([recId, recAction, timestamp], () => {
ID:
{{ record?.id }}
-
+
Nama:
- {{ record.name }}
+ {{ record?.name }}
diff --git a/app/services/supporting-document.service.ts b/app/services/supporting-document.service.ts
index b02cec0e..46eaffa9 100644
--- a/app/services/supporting-document.service.ts
+++ b/app/services/supporting-document.service.ts
@@ -4,11 +4,12 @@ import * as base from './_crud-base'
// Constants
import { encounterClassCodes, uploadCode, type UploadCodeKey } from '~/lib/constants'
-const path = '/api/v1/upload'
-const name = 'upload'
+const path = '/api/v1/encounter-document'
+const create_path = '/api/v1/upload'
+const name = 'encounter-document'
export function create(data: any) {
- return base.create(path, data, name)
+ return base.create(create_path, data, name)
}
export function getList(params: any = null) {
From bb0017ffcb8ec0b0a53b01ffeba3e04d3a247995 Mon Sep 17 00:00:00 2001
From: hasyim_kai
Date: Fri, 14 Nov 2025 15:47:11 +0700
Subject: [PATCH 5/6] Fix: refactor constList Uplaod Doc
---
.../app/document-upload/_common/select-doc-type.vue | 2 +-
app/components/app/document-upload/list.cfg.ts | 5 +++++
app/lib/constants.ts | 10 +++++++---
app/models/encounter-document.ts | 4 ++--
4 files changed, 15 insertions(+), 6 deletions(-)
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 0e86f596..70f78a7b 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 { supportingDocTypeCode, supportingDocOpt, type supportingDocTypeCodeKey } from '~/lib/constants'
+import { docTypeCode, supportingDocOpt, type docTypeCodeKey } from '~/lib/constants'
import { getValueLabelList as getDoctorLabelList } from '~/services/doctor.service'
import { getValueLabelList as getUnitLabelList } from '~/services/unit.service'
diff --git a/app/components/app/document-upload/list.cfg.ts b/app/components/app/document-upload/list.cfg.ts
index 508ad3ea..7f275d31 100644
--- a/app/components/app/document-upload/list.cfg.ts
+++ b/app/components/app/document-upload/list.cfg.ts
@@ -1,5 +1,6 @@
import type { Config } from '~/components/pub/my-ui/data-table'
import { defineAsyncComponent } from 'vue'
+import { docTypeCode, docTypeLabel, type docTypeCodeKey } from '~/lib/constants'
const action = defineAsyncComponent(() => import('~/components/pub/my-ui/data/dropdown-action-dd.vue'))
@@ -22,6 +23,10 @@ export const config: Config = {
],
parses: {
+ type_code: (v: unknown) => {
+ console.log(v)
+ return docTypeLabel[v as docTypeCodeKey]
+ },
},
components: {
diff --git a/app/lib/constants.ts b/app/lib/constants.ts
index 430b2902..48fb5c8c 100644
--- a/app/lib/constants.ts
+++ b/app/lib/constants.ts
@@ -405,17 +405,21 @@ export const encounterDocOpt: { label: string; value: encounterDocTypeCodeKey }[
]
-export const supportingDocTypeCode = {
+export const docTypeCode = {
"encounter-patient": 'encounter-patient',
"encounter-support": 'encounter-support',
"encounter-other": 'encounter-other',
+ "vclaim-sep": 'vclaim-sep',
+ "vclaim-sipp": 'vclaim-sipp',
} as const
-export const supportingDocTypeLabel = {
+export const docTypeLabel = {
"encounter-patient": 'Data Pasien',
"encounter-support": 'Data Penunjang',
"encounter-other": 'Lain - Lain',
+ "vclaim-sep": 'SEP',
+ "vclaim-sipp": 'SIPP',
} as const
-export type supportingDocTypeCodeKey = keyof typeof supportingDocTypeCode
+export type docTypeCodeKey = keyof typeof docTypeCode
export const supportingDocOpt = [
{ label: 'Data Pasien', value: 'encounter-patient' },
{ label: 'Data Penunjang', value: 'encounter-support' },
diff --git a/app/models/encounter-document.ts b/app/models/encounter-document.ts
index c4f9898c..5a98ccd5 100644
--- a/app/models/encounter-document.ts
+++ b/app/models/encounter-document.ts
@@ -1,5 +1,5 @@
import { type Base, genBase } from "./_base"
-import { supportingDocOpt, supportingDocTypeCode, supportingDocTypeLabel, type supportingDocTypeCodeKey } from '~/lib/constants'
+import { docTypeLabel, } from '~/lib/constants'
import { genEmployee, type Employee } from "./employee"
import { genEncounter, type Encounter } from "./encounter"
@@ -21,7 +21,7 @@ export function genEncounterDocument(): EncounterDocument {
encounter: genEncounter(),
upload_employee_id: 0,
employee: genEmployee(),
- type_code: supportingDocTypeLabel["encounter-patient"],
+ type_code: docTypeLabel["encounter-patient"],
name: 'example',
filePath: 'https://bing.com',
fileName: 'example',
From d0aa69d9a155abd09aaeb93c96e2808bddb54dfe Mon Sep 17 00:00:00 2001
From: hasyim_kai
Date: Fri, 14 Nov 2025 17:12:17 +0700
Subject: [PATCH 6/6] Fix: debug table typo Uplaod Doc
---
app/components/app/document-upload/list.cfg.ts | 3 +--
app/components/content/document-upload/list.vue | 6 +++++-
2 files changed, 6 insertions(+), 3 deletions(-)
diff --git a/app/components/app/document-upload/list.cfg.ts b/app/components/app/document-upload/list.cfg.ts
index 7f275d31..979c916d 100644
--- a/app/components/app/document-upload/list.cfg.ts
+++ b/app/components/app/document-upload/list.cfg.ts
@@ -24,8 +24,7 @@ export const config: Config = {
parses: {
type_code: (v: unknown) => {
- console.log(v)
- return docTypeLabel[v as docTypeCodeKey]
+ return docTypeLabel[v?.type_code as docTypeCodeKey]
},
},
diff --git a/app/components/content/document-upload/list.vue b/app/components/content/document-upload/list.vue
index be690871..2ddbb77c 100644
--- a/app/components/content/document-upload/list.vue
+++ b/app/components/content/document-upload/list.vue
@@ -19,7 +19,11 @@ const route = useRoute()
const encounterId = typeof route.params.id == 'string' ? parseInt(route.params.id) : 0
const { data, paginationMeta, handlePageChange, handleSearch, searchInput, fetchData } = usePaginatedList({
- fetchFn: (params) => getList({ 'encounter-id': encounterId, ...params }),
+ fetchFn: (params) => getList({
+ 'encounter-id': encounterId,
+ // includes: "employee",
+ ...params,
+ }),
entityName: 'encounter-document',
})