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
This commit is contained in:
Khafid Prayoga
2025-08-20 14:50:17 +07:00
parent bd98bb815a
commit d913724d62
10 changed files with 286 additions and 13 deletions

No files matched your search

@@ -0,0 +1,13 @@
<script setup lang="ts">
const props = defineProps<{
rec: any
idx?: number
}>()
</script>
<template>
<div class="flex flex-col justify-center">
<p class="font-semibold text-sm">{{ props.rec.patient.name }}</p>
<p class="text-xs text-muted-foreground">{{ props.rec.patient.mrn }}</p>
</div>
</template>
@@ -0,0 +1,29 @@
<script setup lang="ts">
import Badge from './badge.vue'
import { rowStatus } from './list-cfg'
const props = defineProps<{
rec: { status: number }
idx?: number
}>()
const variants = {
0: 'error',
1: 'warning',
2: 'info',
} as const
const statusText = computed(() => {
return rowStatus[props.rec.status as keyof typeof rowStatus]
})
const badgeStatus = computed((): 'error' | 'warning' | 'success' | 'info' => {
return variants[props.rec.status as keyof typeof variants] ?? 'info'
})
</script>
<template>
<div class="flex">
<Badge :status="badgeStatus" :text="statusText" />
</div>
</template>
+36
View File
@@ -0,0 +1,36 @@
<script setup lang="ts">
import { cva } from 'class-variance-authority'
import { cn } from '~/lib/utils'
interface BadgeProps {
status: 'success' | 'info' | 'warning' | 'error'
text: string
}
const props = defineProps<BadgeProps>()
const badgeVariants = cva(
'inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
{
variants: {
variant: {
success: 'rounded-full border-transparent bg-green-500 text-white hover:bg-green-600',
info: 'rounded-full border-transparent bg-blue-500 text-white hover:bg-blue-600',
warning: 'rounded-full border-transparent bg-yellow-500 text-white hover:bg-yellow-600',
error: 'rounded-full border-transparent bg-red-500 text-white hover:bg-red-600',
},
},
defaultVariants: {
variant: 'info',
},
},
)
</script>
<template>
<div class="flex">
<div :class="cn(badgeVariants({ variant: props.status }))">
{{ props.text }}
</div>
</div>
</template>
@@ -0,0 +1,47 @@
<script setup lang="ts">
import Block from '~/components/pub/form/block.vue'
import FieldGroup from '~/components/pub/form/field-group.vue'
import Field from '~/components/pub/form/field.vue'
import Label from '~/components/pub/form/label.vue'
</script>
<template>
<form id="entry-form">
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg xl:text-xl">
<Icon name="i-lucide-user" class="me-2" />
<span class="font-semibold">Tambah</span> Pasien
</div>
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg xl:text-xl">
<Block>
<FieldGroup :column="3">
<Label>Nama</Label>
<Field>
<Input type="text" name="name" />
</Field>
</FieldGroup>
<FieldGroup :column="3">
<Label>Nama</Label>
<Field>
<Input type="text" name="name" />
</Field>
</FieldGroup>
<FieldGroup :column="3">
<Label>Nomor RM</Label>
<Field>
<Input type="text" name="name" />
</Field>
</FieldGroup>
<FieldGroup>
<Label dynamic>Alamat</Label>
<Field>
<Input type="text" name="name" />
</Field>
</FieldGroup>
</Block>
</div>
<div class="my-2 flex justify-end py-2">
<PubNavFooterCsd />
</div>
</form>
</template>
+89
View File
@@ -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 '-'
},
}
+19
View File
@@ -0,0 +1,19 @@
<script setup lang="ts">
import { cols, funcComponent, funcHtml, funcParsed, header, keys } from './list-cfg'
defineProps<{
data: any[]
}>()
</script>
<template>
<PubBaseDataTable
:rows="data"
:cols="cols"
:header="header"
:keys="keys"
:func-parsed="funcParsed"
:func-html="funcHtml"
:func-component="funcComponent"
/>
</template>