From 2275f4dc9991a1e51d0fba31748ff88c85d40bcf Mon Sep 17 00:00:00 2001 From: hasyim_kai Date: Mon, 27 Oct 2025 14:01:58 +0700 Subject: [PATCH] Feat: add UI BPJS > Surat Kontrol --- .../_common/dropdown-action.vue | 90 +++++++ .../control-letter/_common/history-dialog.vue | 49 ++++ .../_common/select-date-range.vue | 104 +++++++++ .../_common/select-destination-polyclinic.vue | 70 ++++++ .../_common/select-origin-polyclinic.vue | 70 ++++++ .../app/bpjs/control-letter/filter.vue | 128 ++++++++++ .../app/bpjs/control-letter/list.cfg.ts | 108 +++++++++ .../app/bpjs/control-letter/list.vue | 31 +++ .../content/bpjs/control-letter/list.vue | 220 ++++++++++++++++++ .../pub/my-ui/badge/status-badge.vue | 26 +++ app/components/pub/my-ui/data/types.ts | 6 + .../pub/my-ui/nav-header/filter-dialog.vue | 85 +++++++ .../pub/my-ui/nav-header/filter.vue | 30 ++- .../integration/bpjs/control-letter/index.vue | 40 ++++ .../(features)/integration/bpjs/sep/add.vue | 12 +- .../(features)/integration/bpjs/sep/index.vue | 6 +- public/side-menu-items/sys.json | 5 + 17 files changed, 1069 insertions(+), 11 deletions(-) create mode 100644 app/components/app/bpjs/control-letter/_common/dropdown-action.vue create mode 100644 app/components/app/bpjs/control-letter/_common/history-dialog.vue create mode 100644 app/components/app/bpjs/control-letter/_common/select-date-range.vue create mode 100644 app/components/app/bpjs/control-letter/_common/select-destination-polyclinic.vue create mode 100644 app/components/app/bpjs/control-letter/_common/select-origin-polyclinic.vue create mode 100644 app/components/app/bpjs/control-letter/filter.vue create mode 100644 app/components/app/bpjs/control-letter/list.cfg.ts create mode 100644 app/components/app/bpjs/control-letter/list.vue create mode 100644 app/components/content/bpjs/control-letter/list.vue create mode 100644 app/components/pub/my-ui/badge/status-badge.vue create mode 100644 app/components/pub/my-ui/nav-header/filter-dialog.vue create mode 100644 app/pages/(features)/integration/bpjs/control-letter/index.vue diff --git a/app/components/app/bpjs/control-letter/_common/dropdown-action.vue b/app/components/app/bpjs/control-letter/_common/dropdown-action.vue new file mode 100644 index 00000000..9086c883 --- /dev/null +++ b/app/components/app/bpjs/control-letter/_common/dropdown-action.vue @@ -0,0 +1,90 @@ + + + diff --git a/app/components/app/bpjs/control-letter/_common/history-dialog.vue b/app/components/app/bpjs/control-letter/_common/history-dialog.vue new file mode 100644 index 00000000..00d7b32f --- /dev/null +++ b/app/components/app/bpjs/control-letter/_common/history-dialog.vue @@ -0,0 +1,49 @@ + + + \ No newline at end of file diff --git a/app/components/app/bpjs/control-letter/_common/select-date-range.vue b/app/components/app/bpjs/control-letter/_common/select-date-range.vue new file mode 100644 index 00000000..114f8542 --- /dev/null +++ b/app/components/app/bpjs/control-letter/_common/select-date-range.vue @@ -0,0 +1,104 @@ + + + diff --git a/app/components/app/bpjs/control-letter/_common/select-destination-polyclinic.vue b/app/components/app/bpjs/control-letter/_common/select-destination-polyclinic.vue new file mode 100644 index 00000000..0852195b --- /dev/null +++ b/app/components/app/bpjs/control-letter/_common/select-destination-polyclinic.vue @@ -0,0 +1,70 @@ + + + diff --git a/app/components/app/bpjs/control-letter/_common/select-origin-polyclinic.vue b/app/components/app/bpjs/control-letter/_common/select-origin-polyclinic.vue new file mode 100644 index 00000000..0852195b --- /dev/null +++ b/app/components/app/bpjs/control-letter/_common/select-origin-polyclinic.vue @@ -0,0 +1,70 @@ + + + diff --git a/app/components/app/bpjs/control-letter/filter.vue b/app/components/app/bpjs/control-letter/filter.vue new file mode 100644 index 00000000..50005069 --- /dev/null +++ b/app/components/app/bpjs/control-letter/filter.vue @@ -0,0 +1,128 @@ + + + diff --git a/app/components/app/bpjs/control-letter/list.cfg.ts b/app/components/app/bpjs/control-letter/list.cfg.ts new file mode 100644 index 00000000..8eb7e5f4 --- /dev/null +++ b/app/components/app/bpjs/control-letter/list.cfg.ts @@ -0,0 +1,108 @@ +import type { Config } from '~/components/pub/my-ui/data-table' +import type { Patient } from '~/models/patient' +import { defineAsyncComponent } from 'vue' +import { educationCodes, genderCodes } from '~/lib/constants' +import { calculateAge } from '~/lib/utils' + +const action = defineAsyncComponent(() => import('./_common/dropdown-action.vue')) +const statusBadge = defineAsyncComponent(() => import('~/components/pub/my-ui/badge/status-badge.vue')) + +export const config: Config = { + cols: [{}, {}, {}, {},{}, {}, {}, {}, {}, {width: 90},{width: 10},], + + headers: [ + [ + { label: 'No Surat' }, + { label: 'No MR' }, + { label: 'Nama' }, + { label: 'Tgl Rencana Kontrol' }, + { label: 'Tgl Penerbitan' }, + { label: 'Klinik Asal' }, + { label: 'Klinik Tujuan' }, + { label: 'DPJP' }, + { label: 'No SEP Asal' }, + { label: 'Status' }, + { label: 'Action' }, + ], + ], + + keys: ['birth_date', 'number', 'person.name', 'birth_date', 'birth_date', + 'birth_date', 'number', 'person.name', 'birth_date', 'status', 'action'], + + delKeyNames: [ + { key: 'code', label: 'Kode' }, + { key: 'name', label: 'Nama' }, + ], + + parses: { + patientId: (rec: unknown): unknown => { + const patient = rec as Patient + return patient.number + }, + identity_number: (rec: unknown): unknown => { + const { person } = rec as Patient + + if (person.nationality == 'WNA') { + return person.passportNumber + } + + return person.residentIdentityNumber || '-' + }, + birth_date: (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 '-' + }, + education: (rec: unknown): unknown => { + 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 '-' + }, + }, + + components: { + action(rec, idx) { + return { + idx, + rec: rec as object, + component: action, + } + }, + status(rec, idx) { + return { + idx, + rec: rec as object, + component: statusBadge, + } + }, + }, + + htmls: { + patient_address(_rec) { + return '-' + }, + }, +} diff --git a/app/components/app/bpjs/control-letter/list.vue b/app/components/app/bpjs/control-letter/list.vue new file mode 100644 index 00000000..8274e752 --- /dev/null +++ b/app/components/app/bpjs/control-letter/list.vue @@ -0,0 +1,31 @@ + + + diff --git a/app/components/content/bpjs/control-letter/list.vue b/app/components/content/bpjs/control-letter/list.vue new file mode 100644 index 00000000..66ed00a5 --- /dev/null +++ b/app/components/content/bpjs/control-letter/list.vue @@ -0,0 +1,220 @@ + + + diff --git a/app/components/pub/my-ui/badge/status-badge.vue b/app/components/pub/my-ui/badge/status-badge.vue new file mode 100644 index 00000000..ba8a7ea6 --- /dev/null +++ b/app/components/pub/my-ui/badge/status-badge.vue @@ -0,0 +1,26 @@ + + + diff --git a/app/components/pub/my-ui/data/types.ts b/app/components/pub/my-ui/data/types.ts index c0d283de..eeadc687 100644 --- a/app/components/pub/my-ui/data/types.ts +++ b/app/components/pub/my-ui/data/types.ts @@ -41,6 +41,12 @@ export interface RefSearchNav { onClear: () => void } +export interface RefExportNav { + onExportPdf?: () => void + onExportCsv?: () => void + onExportExcel?: () => void +} + // prepared header for relatively common usage export interface HeaderPrep { title?: string diff --git a/app/components/pub/my-ui/nav-header/filter-dialog.vue b/app/components/pub/my-ui/nav-header/filter-dialog.vue new file mode 100644 index 00000000..c0d5b854 --- /dev/null +++ b/app/components/pub/my-ui/nav-header/filter-dialog.vue @@ -0,0 +1,85 @@ + + + diff --git a/app/components/pub/my-ui/nav-header/filter.vue b/app/components/pub/my-ui/nav-header/filter.vue index 01b1ab55..811bc0ed 100644 --- a/app/components/pub/my-ui/nav-header/filter.vue +++ b/app/components/pub/my-ui/nav-header/filter.vue @@ -5,11 +5,13 @@ import type { Ref } from 'vue' import type { DateRange } from 'radix-vue' import { CalendarDate, DateFormatter, getLocalTimeZone } from '@internationalized/date' import { cn } from '~/lib/utils' -import type { HeaderPrep, RefSearchNav } from '~/components/pub/my-ui/data/types' +import type { HeaderPrep, RefExportNav, RefSearchNav } from '~/components/pub/my-ui/data/types' const props = defineProps<{ prep: HeaderPrep refSearchNav?: RefSearchNav + enableExport?: boolean + refExportNav?: RefExportNav }>() // function emitSearchNavClick() { @@ -48,7 +50,7 @@ function onFilterClick() { diff --git a/app/pages/(features)/integration/bpjs/control-letter/index.vue b/app/pages/(features)/integration/bpjs/control-letter/index.vue new file mode 100644 index 00000000..8dcb9006 --- /dev/null +++ b/app/pages/(features)/integration/bpjs/control-letter/index.vue @@ -0,0 +1,40 @@ + + + diff --git a/app/pages/(features)/integration/bpjs/sep/add.vue b/app/pages/(features)/integration/bpjs/sep/add.vue index 5db12aac..0658780b 100644 --- a/app/pages/(features)/integration/bpjs/sep/add.vue +++ b/app/pages/(features)/integration/bpjs/sep/add.vue @@ -22,12 +22,12 @@ 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 = true // hasCreateAccess(roleAccess) diff --git a/app/pages/(features)/integration/bpjs/sep/index.vue b/app/pages/(features)/integration/bpjs/sep/index.vue index b8ec57c4..d99dbb5d 100644 --- a/app/pages/(features)/integration/bpjs/sep/index.vue +++ b/app/pages/(features)/integration/bpjs/sep/index.vue @@ -22,9 +22,9 @@ 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 = true // hasReadAccess(roleAccess) diff --git a/public/side-menu-items/sys.json b/public/side-menu-items/sys.json index 19491069..c26d85aa 100644 --- a/public/side-menu-items/sys.json +++ b/public/side-menu-items/sys.json @@ -199,6 +199,11 @@ "title": "Peserta", "icon": "i-lucide-circuit-board", "link": "/integration/bpjs/member" + }, + { + "title": "Surat Kontrol", + "icon": "i-lucide-circuit-board", + "link": "/integration/bpjs/control-letter" } ] },