From b1cb24cae31d6e1b4c602718ceba01730319607c Mon Sep 17 00:00:00 2001 From: Khafid Prayoga Date: Tue, 19 Aug 2025 11:01:28 +0700 Subject: [PATCH 01/15] feat(satusehat): add integration pages and components Add new SatuSehat integration feature including: - Page components for list, add, edit, and detail views - Service status component and type definitions - Summary card component updates for string metrics - RBAC permissions configuration for SatuSehat routes --- app/components/flow/satusehat/list.vue | 107 ++++++++++++++++++ .../pub/base/service-status.type.ts | 7 ++ app/components/pub/base/service-status.vue | 68 +++++++++++ app/components/pub/base/summary-card.type.ts | 2 +- app/components/pub/base/summary-card.vue | 8 +- app/lib/page-permission.ts | 8 ++ .../(features)/satusehat/[id]/detail.vue | 9 ++ app/pages/(features)/satusehat/[id]/edit.vue | 9 ++ app/pages/(features)/satusehat/add.vue | 40 +++++++ app/pages/(features)/satusehat/index.vue | 39 +++++++ 10 files changed, 291 insertions(+), 6 deletions(-) create mode 100644 app/components/flow/satusehat/list.vue create mode 100644 app/components/pub/base/service-status.type.ts create mode 100644 app/components/pub/base/service-status.vue create mode 100644 app/pages/(features)/satusehat/[id]/detail.vue create mode 100644 app/pages/(features)/satusehat/[id]/edit.vue create mode 100644 app/pages/(features)/satusehat/add.vue create mode 100644 app/pages/(features)/satusehat/index.vue diff --git a/app/components/flow/satusehat/list.vue b/app/components/flow/satusehat/list.vue new file mode 100644 index 00000000..c1634333 --- /dev/null +++ b/app/components/flow/satusehat/list.vue @@ -0,0 +1,107 @@ + + + diff --git a/app/components/pub/base/service-status.type.ts b/app/components/pub/base/service-status.type.ts new file mode 100644 index 00000000..f3d15cd0 --- /dev/null +++ b/app/components/pub/base/service-status.type.ts @@ -0,0 +1,7 @@ +export interface ServiceStatus { + serviceName: string + serviceDesc: string + sessionActive: boolean + status: 'connected' | 'connecting' | 'error' | 'disconnected' + isSkeleton?: boolean +} diff --git a/app/components/pub/base/service-status.vue b/app/components/pub/base/service-status.vue new file mode 100644 index 00000000..853e0469 --- /dev/null +++ b/app/components/pub/base/service-status.vue @@ -0,0 +1,68 @@ + + + diff --git a/app/components/pub/base/summary-card.type.ts b/app/components/pub/base/summary-card.type.ts index 64304eaf..be04a9ce 100644 --- a/app/components/pub/base/summary-card.type.ts +++ b/app/components/pub/base/summary-card.type.ts @@ -1,7 +1,7 @@ export interface Summary { title: string icon: Component - metric: number + metric: number | string trend: number timeframe: 'yearly' | 'monthly' | 'weekly' | 'daily' } diff --git a/app/components/pub/base/summary-card.vue b/app/components/pub/base/summary-card.vue index d3413111..d6454627 100644 --- a/app/components/pub/base/summary-card.vue +++ b/app/components/pub/base/summary-card.vue @@ -41,7 +41,7 @@ const isTrending = computed(() => (props.stat?.trend ?? 0) > 0) - + @@ -54,10 +54,8 @@ const isTrending = computed(() => (props.stat?.trend ?? 0) > 0) {{ props.stat.metric.toLocaleString('id-ID') }}

- + {{ props.stat.trend.toFixed(1) }}% diff --git a/app/lib/page-permission.ts b/app/lib/page-permission.ts index 797dd077..3106506a 100644 --- a/app/lib/page-permission.ts +++ b/app/lib/page-permission.ts @@ -9,4 +9,12 @@ export const PAGE_PERMISSIONS = { billing: ['R'], management: ['R'], }, + '/satusehat': { + doctor: ['R'], + nurse: ['R'], + admisi: ['C', 'R', 'U', 'D'], + pharmacy: ['R'], + billing: ['R'], + management: ['R'], + }, } as const satisfies Record diff --git a/app/pages/(features)/satusehat/[id]/detail.vue b/app/pages/(features)/satusehat/[id]/detail.vue new file mode 100644 index 00000000..c1014755 --- /dev/null +++ b/app/pages/(features)/satusehat/[id]/detail.vue @@ -0,0 +1,9 @@ + + + diff --git a/app/pages/(features)/satusehat/[id]/edit.vue b/app/pages/(features)/satusehat/[id]/edit.vue new file mode 100644 index 00000000..351fe46e --- /dev/null +++ b/app/pages/(features)/satusehat/[id]/edit.vue @@ -0,0 +1,9 @@ + + + diff --git a/app/pages/(features)/satusehat/add.vue b/app/pages/(features)/satusehat/add.vue new file mode 100644 index 00000000..d1fee557 --- /dev/null +++ b/app/pages/(features)/satusehat/add.vue @@ -0,0 +1,40 @@ + + + diff --git a/app/pages/(features)/satusehat/index.vue b/app/pages/(features)/satusehat/index.vue new file mode 100644 index 00000000..2c7bb9f5 --- /dev/null +++ b/app/pages/(features)/satusehat/index.vue @@ -0,0 +1,39 @@ + + + From 3e6e773313ba8775f719cb5b53e71ea4323b151d Mon Sep 17 00:00:00 2001 From: Khafid Prayoga Date: Tue, 19 Aug 2025 15:50:17 +0700 Subject: [PATCH 02/15] style: add loader state on acquiring token --- app/components/flow/satusehat/list.vue | 4 +--- app/components/pub/base/service-status.vue | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/app/components/flow/satusehat/list.vue b/app/components/flow/satusehat/list.vue index c1634333..e65e02d8 100644 --- a/app/components/flow/satusehat/list.vue +++ b/app/components/flow/satusehat/list.vue @@ -18,7 +18,7 @@ const refSearchNav: RefSearchNav = { // Loading state management const isLoading = reactive({ - satusehatConn: false, + satusehatConn: true, }) const hreaderPrep: HeaderPrep = { @@ -74,8 +74,6 @@ const summaryData: Summary[] = [ async function callSatuSehat() { try { - isLoading.satusehatConn = true - await new Promise((resolve) => setTimeout(resolve, 3000)) service.status = 'connected' // service.status = 'error' diff --git a/app/components/pub/base/service-status.vue b/app/components/pub/base/service-status.vue index 853e0469..2724e586 100644 --- a/app/components/pub/base/service-status.vue +++ b/app/components/pub/base/service-status.vue @@ -1,6 +1,6 @@ + + From 341c27679c5a590855c54e719bcfdc007c4ee21c Mon Sep 17 00:00:00 2001 From: Khafid Prayoga Date: Tue, 19 Aug 2025 16:51:21 +0700 Subject: [PATCH 04/15] refactor(data-table): improve type safety and rendering logic - Replace ambiguous `object` types with more specific type definitions - Separate HTML rendering from plain text interpolation for better security - Simplify template logic by removing nested ternary operations --- app/components/pub/base/data-table.vue | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/app/components/pub/base/data-table.vue b/app/components/pub/base/data-table.vue index 983559b8..b913affc 100644 --- a/app/components/pub/base/data-table.vue +++ b/app/components/pub/base/data-table.vue @@ -3,12 +3,12 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '~ defineProps<{ rows: unknown[] - cols: object - header: object[] + cols: any[] + header: any[] keys: string[] - funcParsed: object - funcHtml: object - funcComponent: object + funcParsed: Record any> + funcHtml: Record string> + funcComponent: Record any> }>() @@ -37,7 +37,12 @@ defineProps<{ /> From 8fc097d92fe2812f81d7e730e536760057bdbae1 Mon Sep 17 00:00:00 2001 From: Khafid Prayoga Date: Tue, 19 Aug 2025 17:04:49 +0700 Subject: [PATCH 05/15] feat(patient): add status badge component for patient list Implement a new status badge component to visually indicate patient status (active/inactive) in the patient list. The badge uses different colors based on status code and is integrated into the list configuration. --- app/components/app/patient/list-cfg.ts | 12 +++++++++ app/components/app/patient/status-badge.vue | 29 +++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 app/components/app/patient/status-badge.vue diff --git a/app/components/app/patient/list-cfg.ts b/app/components/app/patient/list-cfg.ts index 0e1fe7f4..63455faa 100644 --- a/app/components/app/patient/list-cfg.ts +++ b/app/components/app/patient/list-cfg.ts @@ -4,6 +4,7 @@ import { defineAsyncComponent } from 'vue' type SmallDetailDto = any const action = defineAsyncComponent(() => import('~/components/pub/nav/dropdown-action-dud.vue')) +const statusBadge = defineAsyncComponent(() => import('./status-badge.vue')) export const cols: Col[] = [ {}, @@ -103,6 +104,17 @@ export const funcComponent: RecStrFuncComponent = { } return res }, + status(rec, idx) { + if (rec.status === null) { + rec.status_code = 0 + } + const res: RecComponent = { + idx, + rec: rec as object, + component: statusBadge, + } + return res + }, } export const funcHtml: RecStrFuncUnknown = { diff --git a/app/components/app/patient/status-badge.vue b/app/components/app/patient/status-badge.vue new file mode 100644 index 00000000..32cdfbca --- /dev/null +++ b/app/components/app/patient/status-badge.vue @@ -0,0 +1,29 @@ + + + From bd98bb815a7ec9fbf2f39f65a1ca454b8cf09328 Mon Sep 17 00:00:00 2001 From: Khafid Prayoga Date: Wed, 20 Aug 2025 14:49:59 +0700 Subject: [PATCH 06/15] refactor: update type casting and clean up imports - Add type casting for route.meta.title in index.vue - Update tailwind css path in components.json - Remove unused imports and simplify dashboard component --- app/components/flow/dashboard/index.vue | 12 ++++-------- app/pages/index.vue | 2 +- components.json | 2 +- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/app/components/flow/dashboard/index.vue b/app/components/flow/dashboard/index.vue index 9b591024..2195b428 100644 --- a/app/components/flow/dashboard/index.vue +++ b/app/components/flow/dashboard/index.vue @@ -1,9 +1,5 @@ diff --git a/components.json b/components.json index 9eb92e6b..ede8c33e 100644 --- a/components.json +++ b/components.json @@ -4,7 +4,7 @@ "typescript": true, "tailwind": { "config": "", - "css": "app/assets/css/tailwind.css", + "css": "app/assets/css/main.css", "baseColor": "neutral", "cssVariables": true, "prefix": "" From d913724d62ddb7ace0e350ae1005dcb057612748 Mon Sep 17 00:00:00 2001 From: Khafid Prayoga Date: Wed, 20 Aug 2025 14:50:17 +0700 Subject: [PATCH 07/15] feat(satusehat): add patient list components and integration - Implement badge components for patient and status display - Create list component with configurable table columns - Add entry form for new patient registration - Integrate with existing SatuSehat service flow --- .../app/satusehat/badge-patient.vue | 13 +++ app/components/app/satusehat/badge-status.vue | 29 ++++++ app/components/app/satusehat/badge.vue | 36 ++++++++ app/components/app/satusehat/entry-form.vue | 47 ++++++++++ app/components/app/satusehat/list-cfg.ts | 89 +++++++++++++++++++ app/components/app/satusehat/list.vue | 19 ++++ app/components/app/satusehat/picker.vue | 0 app/components/app/satusehat/search.vue | 0 app/components/flow/satusehat/list.vue | 51 ++++++++++- app/components/pub/base/data-table.vue | 15 ++-- 10 files changed, 286 insertions(+), 13 deletions(-) create mode 100644 app/components/app/satusehat/badge-patient.vue create mode 100644 app/components/app/satusehat/badge-status.vue create mode 100644 app/components/app/satusehat/badge.vue create mode 100644 app/components/app/satusehat/entry-form.vue create mode 100644 app/components/app/satusehat/list-cfg.ts create mode 100644 app/components/app/satusehat/list.vue create mode 100644 app/components/app/satusehat/picker.vue create mode 100644 app/components/app/satusehat/search.vue diff --git a/app/components/app/satusehat/badge-patient.vue b/app/components/app/satusehat/badge-patient.vue new file mode 100644 index 00000000..41202aaf --- /dev/null +++ b/app/components/app/satusehat/badge-patient.vue @@ -0,0 +1,13 @@ + + + diff --git a/app/components/app/satusehat/badge-status.vue b/app/components/app/satusehat/badge-status.vue new file mode 100644 index 00000000..5ff492f0 --- /dev/null +++ b/app/components/app/satusehat/badge-status.vue @@ -0,0 +1,29 @@ + + + diff --git a/app/components/app/satusehat/badge.vue b/app/components/app/satusehat/badge.vue new file mode 100644 index 00000000..197cb084 --- /dev/null +++ b/app/components/app/satusehat/badge.vue @@ -0,0 +1,36 @@ + + + diff --git a/app/components/app/satusehat/entry-form.vue b/app/components/app/satusehat/entry-form.vue new file mode 100644 index 00000000..5768c6a0 --- /dev/null +++ b/app/components/app/satusehat/entry-form.vue @@ -0,0 +1,47 @@ + + + diff --git a/app/components/app/satusehat/list-cfg.ts b/app/components/app/satusehat/list-cfg.ts new file mode 100644 index 00000000..d7d43cae --- /dev/null +++ b/app/components/app/satusehat/list-cfg.ts @@ -0,0 +1,89 @@ +import type { Col, KeyLabel, RecComponent, RecStrFuncComponent, RecStrFuncUnknown, Th } from '../../pub/nav/types' +import { defineAsyncComponent } from 'vue' + +type SmallDetailDto = any + +export const rowType = { + 1: 'Patient', + 2: 'Encounter', + 3: 'Observation', +} + +export const rowStatus = { + 0: 'Gagal', + 1: 'Pending', + 2: 'Terkirim', +} + +const action = defineAsyncComponent(() => import('~/components/pub/nav/dropdown-action-dud.vue')) +const patientBadge = defineAsyncComponent(() => import('./badge-patient.vue')) +const statusBadge = defineAsyncComponent(() => import('./badge-status.vue')) + +export const cols: Col[] = [ + { width: 100 }, + { width: 100 }, + { width: 100 }, + { width: 100 }, + { width: 100 }, + { width: 100 }, + { width: 100 }, +] + +export const header: Th[][] = [ + [ + { label: 'ID' }, + { label: 'Jenis' }, + { label: 'Pasien' }, + { label: 'Status' }, + { label: 'Terakhir Update' }, + { label: 'FHIR ID' }, + { label: '' }, + ], +] + +export const keys = ['id', 'resource_type', 'patient', 'status', 'updated_at', 'fhir_id', 'action'] + +export const delKeyNames: KeyLabel[] = [ + { key: 'code', label: 'Kode' }, + { key: 'name', label: 'Nama' }, +] + +export const funcParsed: RecStrFuncUnknown = { + name: (rec: unknown): unknown => { + const recX = rec as SmallDetailDto + return `${recX.firstName} ${recX.middleName || ''} ${recX.lastName || ''}` + }, +} + +export const funcComponent: RecStrFuncComponent = { + patient(rec, idx) { + const res: RecComponent = { + idx, + rec: rec as object, + component: patientBadge, + } + return res + }, + status(rec, idx) { + const res: RecComponent = { + idx, + rec: rec as object, + component: statusBadge, + } + return res + }, + action(rec, idx) { + const res: RecComponent = { + idx, + rec: rec as object, + component: action, + } + return res + }, +} + +export const funcHtml: RecStrFuncUnknown = { + patient_address(_rec) { + return '-' + }, +} diff --git a/app/components/app/satusehat/list.vue b/app/components/app/satusehat/list.vue new file mode 100644 index 00000000..5b8778d9 --- /dev/null +++ b/app/components/app/satusehat/list.vue @@ -0,0 +1,19 @@ + + + diff --git a/app/components/app/satusehat/picker.vue b/app/components/app/satusehat/picker.vue new file mode 100644 index 00000000..e69de29b diff --git a/app/components/app/satusehat/search.vue b/app/components/app/satusehat/search.vue new file mode 100644 index 00000000..e69de29b diff --git a/app/components/flow/satusehat/list.vue b/app/components/flow/satusehat/list.vue index e65e02d8..2f3ee437 100644 --- a/app/components/flow/satusehat/list.vue +++ b/app/components/flow/satusehat/list.vue @@ -4,6 +4,42 @@ import type { Summary } from '~/components/pub/base/summary-card.type' import type { HeaderPrep, RefSearchNav } from '~/components/pub/nav/types' import { CircleCheckBig, CircleDashed, CircleX, Send } from 'lucide-vue-next' +const data = ref([ + { + id: 'RSC001', + resource_type: 'Encounter', + patient: { + name: 'Ahmad Wepe', + mrn: 'RM001234', + }, + status: 2, + updated_at: '2025-03-12', + fhir_id: 'ENC-00123', + }, + { + id: 'RSC002', + resource_type: 'Encounter', + patient: { + name: 'Siti Aminah', + mrn: 'RM001235', + }, + status: 1, + updated_at: '2025-03-10', + fhir_id: 'ENC-001235', + }, + { + id: 'RSC003', + resource_type: 'Encounter', + patient: { + name: 'Budi Antono', + mrn: 'RM001236', + }, + status: 0, + updated_at: '2025-03-11', + fhir_id: 'ENC-001236', + }, +]) + const refSearchNav: RefSearchNav = { onClick: () => { // open filter modal @@ -16,12 +52,16 @@ const refSearchNav: RefSearchNav = { }, } +const recId = ref(0) +const recAction = ref('') +const recItem = ref(null) + // Loading state management const isLoading = reactive({ satusehatConn: true, }) -const hreaderPrep: HeaderPrep = { +const headerPrep: HeaderPrep = { title: 'SATUSEHAT Integration', icon: 'i-lucide-box', addNav: { @@ -74,7 +114,7 @@ const summaryData: Summary[] = [ async function callSatuSehat() { try { - await new Promise((resolve) => setTimeout(resolve, 3000)) + await new Promise((resolve) => setTimeout(resolve, 500)) service.status = 'connected' // service.status = 'error' service.sessionActive = true @@ -87,10 +127,14 @@ async function callSatuSehat() { onMounted(() => { callSatuSehat() }) + +provide('rec_id', recId) +provide('rec_action', recAction) +provide('rec_item', recItem) + diff --git a/app/components/pub/base/data-table.vue b/app/components/pub/base/data-table.vue index b913affc..31fa7050 100644 --- a/app/components/pub/base/data-table.vue +++ b/app/components/pub/base/data-table.vue @@ -14,13 +14,11 @@ defineProps<{