From c98018bb4eaacbcd05763f404b40d3d4a27899dd Mon Sep 17 00:00:00 2001 From: hasyim_kai Date: Tue, 18 Nov 2025 12:58:58 +0700 Subject: [PATCH] Squashed commit of the following: commit bcfb4c1456b7b58c63d4969985200ceca72aee16 Merge: 1cbde57 975c87d Author: Munawwirul Jamal <57973347+munaja@users.noreply.github.com> Date: Mon Nov 17 11:15:14 2025 +0700 Merge pull request #147 from dikstub-rssa/feat/surat-kontrol-135 Feat: Integration Rehab Medik - Surat Kontrol commit 975c87d99af0471f62111a455fa214abc1f2e998 Merge: f582090 1cbde57 Author: hasyim_kai Date: Mon Nov 17 10:58:10 2025 +0700 Merge branch 'dev' into feat/surat-kontrol-135 commit f582090d18fe797e9f7e0e5b8559b1e413c7c921 Author: hasyim_kai Date: Thu Nov 13 11:56:21 2025 +0700 Fix: Refactor surat kontrol commit a14c4a5d3c334d3ea7b9875feb5620991511d4f0 Author: hasyim_kai Date: Tue Nov 11 14:21:58 2025 +0700 Fix: Refactor Surat Kontrol CRUD {id} to {code} commit 24313adef6bd3db52f23ace0675100bea1aaefad Author: hasyim_kai Date: Fri Nov 7 10:35:46 2025 +0700 Fix: debug back btn in add, edit, detail content page commit 59b44b5729161b3e7c014ea440f17bf98fd8b954 Merge: 99a61a0 db15ec9 Author: Muhammad Hasyim Chaidir Ali <68959522+Hasyim-Kai@users.noreply.github.com> Date: Fri Nov 7 09:11:10 2025 +0700 Merge branch 'dev' into feat/surat-kontrol-135 commit 99a61a0bf2edf2f924d0424600e94a1d64901e48 Author: hasyim_kai Date: Thu Nov 6 08:06:01 2025 +0700 Feat: add right & bottom label in input base component commit db48919325a9c3a7940cb208fee71c1d42ee9a8a Author: hasyim_kai Date: Wed Nov 5 13:53:43 2025 +0700 Feat: add banner in List if requirement not met commit bd57250f7e9bcaed8e11f6533435e3c788347286 Author: hasyim_kai Date: Wed Nov 5 13:26:48 2025 +0700 Fix: refactor getDetail url param commit a361922e32f2e8a649edaedd9cec82131aff2793 Author: hasyim_kai Date: Wed Nov 5 13:19:07 2025 +0700 Feat: Add & integrate add, edit, detail page commit 331f4a6b20194964d89eb1ada2d7661d8be8f76d Author: hasyim_kai Date: Tue Nov 4 16:56:08 2025 +0700 Feat: Integrate Control Letter commit 2275f4dc9991a1e51d0fba31748ff88c85d40bcf Author: hasyim_kai Date: Mon Oct 27 14:01:58 2025 +0700 Feat: add UI BPJS > Surat Kontrol commit 89e0e7a2c8a20ae31ca381d3320bd81755b73c34 Author: hasyim_kai Date: Mon Oct 27 10:21:59 2025 +0700 Feat: add UI CRUD Surat Kontrol at Rehab Medik > kunjungan > Proses --- .../_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 +++ .../control-letter/_common/select-date.vue | 116 +++++++++ .../control-letter/_common/select-dpjp.vue | 98 ++++++++ .../_common/select-specialist.vue | 98 ++++++++ .../_common/select-subspecialist.vue | 97 ++++++++ .../control-letter/_common/select-unit.vue | 85 +++++++ .../app/control-letter/entry-form.vue | 94 ++++++++ app/components/app/control-letter/list.cfg.ts | 64 +++++ app/components/app/control-letter/list.vue | 31 +++ app/components/app/control-letter/preview.vue | 54 +++++ .../content/bpjs/control-letter/list.vue | 220 ++++++++++++++++++ app/components/content/control-letter/add.vue | 133 +++++++++++ .../content/control-letter/detail.vue | 79 +++++++ .../content/control-letter/edit.vue | 162 +++++++++++++ .../content/control-letter/list.vue | 176 ++++++++++++++ app/components/content/encounter/process.vue | 3 +- .../pub/my-ui/alert/warning-alert.vue | 27 +++ .../pub/my-ui/badge/status-badge.vue | 26 +++ .../pub/my-ui/confirmation/confirmation.vue | 2 +- app/components/pub/my-ui/data/types.ts | 6 + app/components/pub/my-ui/form/input-base.vue | 6 +- .../pub/my-ui/nav-header/filter-dialog.vue | 85 +++++++ .../pub/my-ui/nav-header/filter.vue | 30 ++- app/handlers/control-letter.handler.ts | 24 ++ app/lib/date.ts | 8 + app/models/control-letter.ts | 37 +++ app/models/doctor.ts | 13 +- .../integration/bpjs/control-letter/index.vue | 40 ++++ .../(features)/integration/bpjs/sep/add.vue | 12 +- .../(features)/integration/bpjs/sep/index.vue | 6 +- .../outpatient/encounter/[id]/index.vue | 41 ++++ .../[control_letter_id]/edit.vue | 41 ++++ .../[control_letter_id]/index.vue | 41 ++++ .../encounter/[id]/control-letter/add.vue | 42 ++++ app/schemas/control-letter.schema.ts | 47 ++++ app/services/doctor.service.ts | 10 +- app/services/specialist.service.ts | 6 +- app/services/subspecialist.service.ts | 6 +- app/services/unit.service.ts | 6 +- public/side-menu-items/system.json | 5 + 47 files changed, 2696 insertions(+), 31 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/app/control-letter/_common/select-date.vue create mode 100644 app/components/app/control-letter/_common/select-dpjp.vue create mode 100644 app/components/app/control-letter/_common/select-specialist.vue create mode 100644 app/components/app/control-letter/_common/select-subspecialist.vue create mode 100644 app/components/app/control-letter/_common/select-unit.vue create mode 100644 app/components/app/control-letter/entry-form.vue create mode 100644 app/components/app/control-letter/list.cfg.ts create mode 100644 app/components/app/control-letter/list.vue create mode 100644 app/components/app/control-letter/preview.vue create mode 100644 app/components/content/bpjs/control-letter/list.vue create mode 100644 app/components/content/control-letter/add.vue create mode 100644 app/components/content/control-letter/detail.vue create mode 100644 app/components/content/control-letter/edit.vue create mode 100644 app/components/content/control-letter/list.vue create mode 100644 app/components/pub/my-ui/alert/warning-alert.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/handlers/control-letter.handler.ts create mode 100644 app/models/control-letter.ts create mode 100644 app/pages/(features)/integration/bpjs/control-letter/index.vue create mode 100644 app/pages/(features)/outpatient/encounter/[id]/index.vue create mode 100644 app/pages/(features)/rehab/encounter/[id]/control-letter/[control_letter_id]/edit.vue create mode 100644 app/pages/(features)/rehab/encounter/[id]/control-letter/[control_letter_id]/index.vue create mode 100644 app/pages/(features)/rehab/encounter/[id]/control-letter/add.vue create mode 100644 app/schemas/control-letter.schema.ts 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/app/control-letter/_common/select-date.vue b/app/components/app/control-letter/_common/select-date.vue new file mode 100644 index 00000000..057d0a63 --- /dev/null +++ b/app/components/app/control-letter/_common/select-date.vue @@ -0,0 +1,116 @@ + + + diff --git a/app/components/app/control-letter/_common/select-dpjp.vue b/app/components/app/control-letter/_common/select-dpjp.vue new file mode 100644 index 00000000..2053ebdb --- /dev/null +++ b/app/components/app/control-letter/_common/select-dpjp.vue @@ -0,0 +1,98 @@ + + + diff --git a/app/components/app/control-letter/_common/select-specialist.vue b/app/components/app/control-letter/_common/select-specialist.vue new file mode 100644 index 00000000..cd5ee923 --- /dev/null +++ b/app/components/app/control-letter/_common/select-specialist.vue @@ -0,0 +1,98 @@ + + + diff --git a/app/components/app/control-letter/_common/select-subspecialist.vue b/app/components/app/control-letter/_common/select-subspecialist.vue new file mode 100644 index 00000000..61567c0c --- /dev/null +++ b/app/components/app/control-letter/_common/select-subspecialist.vue @@ -0,0 +1,97 @@ + + + diff --git a/app/components/app/control-letter/_common/select-unit.vue b/app/components/app/control-letter/_common/select-unit.vue new file mode 100644 index 00000000..afe0ca0a --- /dev/null +++ b/app/components/app/control-letter/_common/select-unit.vue @@ -0,0 +1,85 @@ + + + diff --git a/app/components/app/control-letter/entry-form.vue b/app/components/app/control-letter/entry-form.vue new file mode 100644 index 00000000..2517e8b1 --- /dev/null +++ b/app/components/app/control-letter/entry-form.vue @@ -0,0 +1,94 @@ + + + diff --git a/app/components/app/control-letter/list.cfg.ts b/app/components/app/control-letter/list.cfg.ts new file mode 100644 index 00000000..3eb7bd84 --- /dev/null +++ b/app/components/app/control-letter/list.cfg.ts @@ -0,0 +1,64 @@ +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('~/components/pub/my-ui/data/dropdown-action-dud.vue')) + +export const config: Config = { + cols: [{width: 180}, {}, {}, {}, {}, {width: 30},], + + headers: [ + [ + { label: 'Tgl Rencana Kontrol' }, + { label: 'Spesialis' }, + { label: 'Sub Spesialis' }, + { label: 'DPJP' }, + { label: 'Status SEP' }, + { label: 'Action' }, + ], + ], + + keys: ['date', 'specialist.name', 'subspecialist.name', 'doctor.employee.person.name', 'sep_status', 'action'], + + delKeyNames: [ + { key: 'code', label: 'Kode' }, + { key: 'name', label: 'Nama' }, + ], + + parses: { + date: (rec: unknown): unknown => { + const date = (rec as any).date + if (typeof date == 'object' && date) { + return (date as Date).toLocaleDateString('id-ID') + } else if (typeof date == 'string') { + return (date as string).substring(0, 10) + } + return date + }, + specialist_subspecialist: (rec: unknown): unknown => { + return '-' + }, + dpjp: (rec: unknown): unknown => { + // const { person } = rec as Patient + return '-' + }, + }, + + components: { + action(rec, idx) { + return { + idx, + rec: rec as object, + component: action, + } + }, + }, + + htmls: { + sep_status(_rec) { + return 'SEP Internal' + }, + }, +} diff --git a/app/components/app/control-letter/list.vue b/app/components/app/control-letter/list.vue new file mode 100644 index 00000000..8274e752 --- /dev/null +++ b/app/components/app/control-letter/list.vue @@ -0,0 +1,31 @@ + + + diff --git a/app/components/app/control-letter/preview.vue b/app/components/app/control-letter/preview.vue new file mode 100644 index 00000000..e10a2b91 --- /dev/null +++ b/app/components/app/control-letter/preview.vue @@ -0,0 +1,54 @@ + + + + + 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/content/control-letter/add.vue b/app/components/content/control-letter/add.vue new file mode 100644 index 00000000..44f03a2f --- /dev/null +++ b/app/components/content/control-letter/add.vue @@ -0,0 +1,133 @@ + + + + + diff --git a/app/components/content/control-letter/detail.vue b/app/components/content/control-letter/detail.vue new file mode 100644 index 00000000..d9019d57 --- /dev/null +++ b/app/components/content/control-letter/detail.vue @@ -0,0 +1,79 @@ + + + diff --git a/app/components/content/control-letter/edit.vue b/app/components/content/control-letter/edit.vue new file mode 100644 index 00000000..99a5c282 --- /dev/null +++ b/app/components/content/control-letter/edit.vue @@ -0,0 +1,162 @@ + + + + + diff --git a/app/components/content/control-letter/list.vue b/app/components/content/control-letter/list.vue new file mode 100644 index 00000000..c9353057 --- /dev/null +++ b/app/components/content/control-letter/list.vue @@ -0,0 +1,176 @@ + + + diff --git a/app/components/content/encounter/process.vue b/app/components/content/encounter/process.vue index 267fee95..3b57e7f6 100644 --- a/app/components/content/encounter/process.vue +++ b/app/components/content/encounter/process.vue @@ -20,6 +20,7 @@ import Radiology from '~/components/content/radiology-order/main.vue' import Consultation from '~/components/content/consultation/list.vue' import DocUploadList from '~/components/content/document-upload/list.vue' import { genEncounter } from '~/models/encounter' +import ControlLetterList from '~/components/content/control-letter/list.vue' const route = useRoute() const router = useRouter() @@ -80,7 +81,7 @@ const tabs: TabItem[] = [ { value: 'mcu-result', label: 'Hasil Penunjang' }, { value: 'consultation', label: 'Konsultasi', component: Consultation, props: { encounter: data } }, { value: 'resume', label: 'Resume' }, - { value: 'control', label: 'Surat Kontrol' }, + { value: 'control', label: 'Surat Kontrol', component: ControlLetterList, props: { encounter: data } }, { value: 'screening', label: 'Skrinning MPP' }, { value: 'supporting-document', label: 'Upload Dokumen Pendukung', component: DocUploadList, props: { encounter: data, }, }, ] diff --git a/app/components/pub/my-ui/alert/warning-alert.vue b/app/components/pub/my-ui/alert/warning-alert.vue new file mode 100644 index 00000000..afdbe7ae --- /dev/null +++ b/app/components/pub/my-ui/alert/warning-alert.vue @@ -0,0 +1,27 @@ + + + \ No newline at end of file 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/confirmation/confirmation.vue b/app/components/pub/my-ui/confirmation/confirmation.vue index 590f328d..b5c328aa 100644 --- a/app/components/pub/my-ui/confirmation/confirmation.vue +++ b/app/components/pub/my-ui/confirmation/confirmation.vue @@ -71,7 +71,7 @@ function handleCancel() {
-
+

diff --git a/app/components/pub/my-ui/data/types.ts b/app/components/pub/my-ui/data/types.ts index a9b2586b..f27a5578 100644 --- a/app/components/pub/my-ui/data/types.ts +++ b/app/components/pub/my-ui/data/types.ts @@ -42,6 +42,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/form/input-base.vue b/app/components/pub/my-ui/form/input-base.vue index aeb4a4af..a3743734 100644 --- a/app/components/pub/my-ui/form/input-base.vue +++ b/app/components/pub/my-ui/form/input-base.vue @@ -19,6 +19,8 @@ const props = defineProps<{ maxLength?: number isRequired?: boolean isDisabled?: boolean + rightLabel?: string + bottomLabel?: string }>() function handleInput(event: Event) { @@ -61,7 +63,7 @@ function handleInput(event: Event) { v-slot="{ componentField }" :name="fieldName" > - + +

{{ rightLabel }}

+

{{ bottomLabel }}

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 ab28620b..74f6d8dc 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() { @@ -57,7 +59,7 @@ function onFilterClick() { diff --git a/app/handlers/control-letter.handler.ts b/app/handlers/control-letter.handler.ts new file mode 100644 index 00000000..b096a178 --- /dev/null +++ b/app/handlers/control-letter.handler.ts @@ -0,0 +1,24 @@ +// Handlers +import { genCrudHandler } from '~/handlers/_handler' + +// Services +import { create, update, remove } from '~/services/control-letter.service' + +export const { + recId, + recAction, + recItem, + isReadonly, + isProcessing, + isFormEntryDialogOpen, + isRecordConfirmationOpen, + onResetState, + handleActionSave, + handleActionEdit, + handleActionRemove, + handleCancelForm, +} = genCrudHandler({ + create, + update, + remove, +}) diff --git a/app/lib/date.ts b/app/lib/date.ts index 502a6cfb..2c7b92cf 100644 --- a/app/lib/date.ts +++ b/app/lib/date.ts @@ -41,4 +41,12 @@ export function getAge(dateString: string, comparedDate?: string): { idFormat: s idFormat, extFormat }; +} + +export function formatDateYyyyMmDd(isoDateString: string): string { + const date = new Date(isoDateString); + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + return `${year}-${month}-${day}`; } \ No newline at end of file diff --git a/app/models/control-letter.ts b/app/models/control-letter.ts new file mode 100644 index 00000000..8f520212 --- /dev/null +++ b/app/models/control-letter.ts @@ -0,0 +1,37 @@ +import { type Base, genBase } from "./_base" +import { genDoctor, type Doctor } from "./doctor" +import { genEncounter, type Encounter } from "./encounter" +import { genSpecialist, type Specialist } from "./specialist" +import { genSubspecialist, type Subspecialist } from "./subspecialist" +import { genUnit, type Unit } from "./unit" + +export interface ControlLetter extends Base { + encounter_id: number + encounter: Encounter + unit_id: number + unit: Unit + specialist_id: number + specialist: Specialist + subspecialist_id: number + subspecialist: Subspecialist + doctor_id: number + doctor: Doctor + date: '' +} + +export function genControlLetter(): ControlLetter { + return { + ...genBase(), + encounter_id: 0, + encounter: genEncounter(), + unit_id: 0, + unit: genUnit(), + specialist_id: 0, + specialist: genSpecialist(), + subspecialist_id: 0, + subspecialist: genSubspecialist(), + doctor_id: 0, + doctor: genDoctor(), + date: '' + } +} diff --git a/app/models/doctor.ts b/app/models/doctor.ts index 3f517476..1b631907 100644 --- a/app/models/doctor.ts +++ b/app/models/doctor.ts @@ -8,10 +8,11 @@ export interface Doctor extends Base { employee: Employee ihs_number: string sip_number: string - unit_id?: number - specialist_id?: number + code?: string + unit_icode?: number + specialist_icode?: number specialist?: Specialist - subspecialist_id?: number + subspecialist_icode?: number subspecialist?: Subspecialist bpjs_code?: string } @@ -21,9 +22,9 @@ export interface CreateDto { employee_id: number ihs_number: string sip_number: string - unit_id?: number - specialist_id?: number - subspecialist_id?: number + unit_code?: number + specialist_code?: number + subspecialist_code?: number bpjs_code: string } 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/app/pages/(features)/outpatient/encounter/[id]/index.vue b/app/pages/(features)/outpatient/encounter/[id]/index.vue new file mode 100644 index 00000000..1864cf2c --- /dev/null +++ b/app/pages/(features)/outpatient/encounter/[id]/index.vue @@ -0,0 +1,41 @@ + + + diff --git a/app/pages/(features)/rehab/encounter/[id]/control-letter/[control_letter_id]/edit.vue b/app/pages/(features)/rehab/encounter/[id]/control-letter/[control_letter_id]/edit.vue new file mode 100644 index 00000000..cc5d182f --- /dev/null +++ b/app/pages/(features)/rehab/encounter/[id]/control-letter/[control_letter_id]/edit.vue @@ -0,0 +1,41 @@ + + + diff --git a/app/pages/(features)/rehab/encounter/[id]/control-letter/[control_letter_id]/index.vue b/app/pages/(features)/rehab/encounter/[id]/control-letter/[control_letter_id]/index.vue new file mode 100644 index 00000000..612315ad --- /dev/null +++ b/app/pages/(features)/rehab/encounter/[id]/control-letter/[control_letter_id]/index.vue @@ -0,0 +1,41 @@ + + + diff --git a/app/pages/(features)/rehab/encounter/[id]/control-letter/add.vue b/app/pages/(features)/rehab/encounter/[id]/control-letter/add.vue new file mode 100644 index 00000000..1070a29f --- /dev/null +++ b/app/pages/(features)/rehab/encounter/[id]/control-letter/add.vue @@ -0,0 +1,42 @@ + + + diff --git a/app/schemas/control-letter.schema.ts b/app/schemas/control-letter.schema.ts new file mode 100644 index 00000000..c82ffaac --- /dev/null +++ b/app/schemas/control-letter.schema.ts @@ -0,0 +1,47 @@ +import { z } from 'zod' + +const ControlLetterSchema = z.object({ + sepStatus: z.string({ + required_error: 'Mohon isi status SEP', + }).default('SEP Internal'), + unit_code: z.string({ + required_error: 'Mohon isi Unit', + }), + specialist_code: z.string({ + required_error: 'Mohon isi Spesialis', + }), + subspecialist_code: z.string({ + required_error: 'Mohon isi Sub Spesialis', + }), + doctor_code: z.string({ + required_error: 'Mohon isi DPJP', + }), + encounter_code: z.string().optional(), + date: z.string({ + required_error: 'Mohon lengkapi Tanggal Kontrol', + }) + .refine( + (date) => { + // Jika kosong, return false untuk required validation + if (!date || date.trim() === '') return false + + // Jika ada isi, validasi format tanggal + try { + const dateObj = new Date(date) + // Cek apakah tanggal valid dan tahun >= 1900 + return !isNaN(dateObj.getTime()) && dateObj.getFullYear() >= 1900 + } catch { + return false + } + }, + { + message: 'Mohon lengkapi Tanggal Kontrol dengan format yang valid', + }, + ) + .transform((dateStr) => new Date(dateStr).toISOString()), +}) + +type ControlLetterFormData = z.infer + +export { ControlLetterSchema } +export type { ControlLetterFormData } diff --git a/app/services/doctor.service.ts b/app/services/doctor.service.ts index 74104c2c..e6ae0051 100644 --- a/app/services/doctor.service.ts +++ b/app/services/doctor.service.ts @@ -1,8 +1,6 @@ // Base import * as base from './_crud-base' - -// Types -import type { Doctor } from '~/models/doctor' +import type { Doctor } from "~/models/doctor"; const path = '/api/v1/doctor' const name = 'doctor' @@ -27,13 +25,15 @@ export function remove(id: number | string) { return base.remove(path, id, name) } -export async function getValueLabelList(params: any = null): Promise<{ value: string; label: string }[]> { +export async function getValueLabelList(params: any = null, useCodeAsValue = false): Promise<{ value: string; label: string }[]> { let data: { value: string; label: string }[] = [] const result = await getList(params) if (result.success) { const resultData = result.body?.data || [] data = resultData.map((item: Doctor) => ({ - value: item.id ? String(item.id) : '', + value: useCodeAsValue ? item.code + : item.id ? Number(item.id) + : item.id, label: item.employee?.person?.name || '', })) } diff --git a/app/services/specialist.service.ts b/app/services/specialist.service.ts index b18eac34..d4c81b5c 100644 --- a/app/services/specialist.service.ts +++ b/app/services/specialist.service.ts @@ -28,13 +28,15 @@ export function remove(id: number | string) { return base.remove(path, id, name) } -export async function getValueLabelList(params: any = null): Promise<{ value: string; label: string }[]> { +export async function getValueLabelList(params: any = null, useCodeAsValue = false): Promise<{ value: string; label: string }[]> { let data: { value: string; label: string }[] = [] const result = await getList(params) if (result.success) { const resultData = result.body?.data || [] data = resultData.map((item: Specialist) => ({ - value: item.id ? Number(item.id) : item.code, + value: useCodeAsValue ? item.code + : item.id ? Number(item.id) + : item.id, label: item.name, parent: item.unit_id ? Number(item.unit_id) : null, })) diff --git a/app/services/subspecialist.service.ts b/app/services/subspecialist.service.ts index e384f059..f13c715f 100644 --- a/app/services/subspecialist.service.ts +++ b/app/services/subspecialist.service.ts @@ -27,13 +27,15 @@ export function remove(id: number | string) { return base.remove(path, id, name) } -export async function getValueLabelList(params: any = null): Promise<{ value: string; label: string }[]> { +export async function getValueLabelList(params: any = null, useCodeAsValue = false): Promise<{ value: string; label: string }[]> { let data: { value: string; label: string }[] = [] const result = await getList(params) if (result.success) { const resultData = result.body?.data || [] data = resultData.map((item: Subspecialist) => ({ - value: item.id ? Number(item.id) : item.code, + value: useCodeAsValue ? item.code + : item.id ? Number(item.id) + : item.id, label: item.name, parent: item.specialist_id ? Number(item.specialist_id) : null, })) diff --git a/app/services/unit.service.ts b/app/services/unit.service.ts index ec1ccec0..402504b6 100644 --- a/app/services/unit.service.ts +++ b/app/services/unit.service.ts @@ -27,13 +27,15 @@ export function remove(id: number | string) { return base.remove(path, id, name) } -export async function getValueLabelList(params: any = null): Promise<{ value: string; label: string }[]> { +export async function getValueLabelList(params: any = null, useCodeAsValue = false): Promise<{ value: string; label: string }[]> { let data: { value: string; label: string }[] = [] const result = await getList(params) if (result.success) { const resultData = result.body?.data || [] data = resultData.map((item: Unit) => ({ - value: item.id, + value: useCodeAsValue ? item.code + : item.id ? Number(item.id) + : item.id, label: item.name, })) } diff --git a/public/side-menu-items/system.json b/public/side-menu-items/system.json index d5e4fbb4..31890951 100644 --- a/public/side-menu-items/system.json +++ b/public/side-menu-items/system.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" } ] },