feat (encounter): add entry

This commit is contained in:
Abizrh
2025-09-23 19:41:14 +07:00
parent 18e6824611
commit 688e79680f
15 changed files with 849 additions and 35 deletions
+9 -4
View File
@@ -22,11 +22,16 @@ function handlePageChange(page: number) {
<template>
<div class="space-y-4">
<PubBaseDataTable
:rows="data" :cols="cols" :header="header" :keys="keys" :func-parsed="funcParsed"
:func-html="funcHtml" :func-component="funcComponent" :skeleton-size="paginationMeta?.pageSize"
:rows="data"
:cols="cols"
:header="header"
:keys="keys"
:func-parsed="funcParsed"
:func-html="funcHtml"
:func-component="funcComponent"
:skeleton-size="paginationMeta?.pageSize"
/>
<!-- FIXME: pindahkan ke content/division/list.vue -->
<PaginationView :pagination-meta="paginationMeta" @page-change="handlePageChange" />
</div>
</template>
+338
View File
@@ -0,0 +1,338 @@
<script setup lang="ts">
import type { FormErrors } from '~/types/error'
import { toTypedSchema } from '@vee-validate/zod'
import Block from '~/components/pub/custom-ui/form/block.vue'
import Combobox from '~/components/pub/custom-ui/form/combobox.vue'
import FieldGroup from '~/components/pub/custom-ui/form/field-group.vue'
import Field from '~/components/pub/custom-ui/form/field.vue'
import Label from '~/components/pub/custom-ui/form/label.vue'
import { Form } from '~/components/pub/ui/form'
import DatepickerSingle from '~/components/pub/custom-ui/form/datepicker-single.vue'
import { educationCodes, genderCodes, occupationCodes, religionCodes, relationshipCodes } from '~/lib/constants'
import { mapToComboboxOptList } from '~/lib/utils'
interface DivisionFormData {
name: string
code: string
parentId: string
}
const props = defineProps<{
division: {
msg: {
placeholder: string
search: string
empty: string
}
}
items: {
value: string
label: string
code: string
}[]
schema: any
initialValues?: Partial<DivisionFormData>
errors?: FormErrors
}>()
const emit = defineEmits<{
submit: [values: DivisionFormData, resetForm: () => void]
cancel: [resetForm: () => void]
}>()
const relationshipOpts = mapToComboboxOptList(relationshipCodes)
const educationOpts = mapToComboboxOptList(educationCodes)
const occupationOpts = mapToComboboxOptList(occupationCodes)
const genderOpts = mapToComboboxOptList(genderCodes)
const formSchema = toTypedSchema(props.schema)
// Form submission handler
function onSubmitForm(values: any, { resetForm }: { resetForm: () => void }) {
const formData: DivisionFormData = {
name: values.name || '',
code: values.code || '',
parentId: values.parentId || '',
}
emit('submit', formData, resetForm)
}
const doctorOpts = ref([
{ label: 'Pilih', value: null },
{ label: 'Dr. A', value: 1 },
])
const paymentOpts = ref([
{ label: 'Umum', value: 'umum' },
{ label: 'BPJS', value: 'bpjs' },
])
const sepOpts = ref([
{ label: 'Rujukan Internal', value: 'ri' },
{ label: 'SEP Rujukan', value: 'sr' },
])
// file refs untuk tombol "Pilih Berkas"
const sepFileInput = ref<HTMLInputElement | null>(null)
const sippFileInput = ref<HTMLInputElement | null>(null)
function pickSepFile() {
sepFileInput.value?.click()
}
function pickSippFile() {
sippFileInput.value?.click()
}
function onSepFileChange(e: Event) {
const f = (e.target as HTMLInputElement).files?.[0]
// set ke form / emit / simpan di state sesuai form library-mu
console.log('sep file', f)
}
function onSippFileChange(e: Event) {
const f = (e.target as HTMLInputElement).files?.[0]
console.log('sipp file', f)
}
function onAddSep() {
// contoh handler tombol "+" di sebelah No. SEP
console.log('open modal tambah SEP')
}
</script>
<template>
<Form
v-slot="{ handleSubmit, resetForm }"
as=""
keep-values
:validation-schema="formSchema"
:initial-values="initialValues"
>
<form id="entry-form" @submit="handleSubmit($event, (values) => onSubmitForm(values, { resetForm }))">
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg xl:text-xl">
<div class="flex flex-col justify-between">
<div class="p-2">
<h2 class="text-md font-semibold">Data Pasien</h2>
</div>
<Block>
<FieldGroup :column="3">
<Label label-for="patient_name">Nama Pasien</Label>
<Field id="patient_name" :errors="errors">
<FormField v-slot="{ componentField }" name="patient_name">
<FormItem>
<FormControl>
<Input
id="patient_name"
v-bind="componentField"
disabled
placeholder="Tambah data pasien terlebih dahulu"
/>
</FormControl>
<FormMessage />
</FormItem>
</FormField>
</Field>
</FieldGroup>
<!-- NIK -->
<FieldGroup :column="3">
<Label label-for="nik">NIK</Label>
<Field id="nik" :errors="errors">
<FormField v-slot="{ componentField }" name="nik">
<FormItem>
<FormControl>
<Input id="nik" v-bind="componentField" disabled placeholder="Otomatis" />
</FormControl>
<FormMessage />
</FormItem>
</FormField>
</Field>
</FieldGroup>
<!-- No. RM -->
<FieldGroup :column="3">
<Label label-for="rm">No. RM</Label>
<Field id="rm" :errors="errors">
<FormField v-slot="{ componentField }" name="rm">
<FormItem>
<FormControl>
<Input id="rm" v-bind="componentField" disabled placeholder="RM99222" />
</FormControl>
<FormMessage />
</FormItem>
</FormField>
</Field>
</FieldGroup>
<Separator />
</Block>
<div class="p-2">
<h2 class="text-md font-semibold">Data Kunjungan</h2>
</div>
<Block>
<!-- Dokter (Combobox) -->
<FieldGroup :column="3">
<Label label-for="doctor_id">Dokter</Label>
<Field id="doctor_id" :errors="errors">
<FormField v-slot="{ componentField }" name="doctor_id">
<FormItem>
<FormControl>
<Combobox id="doctor_id" v-bind="componentField" :items="doctorOpts" />
</FormControl>
<FormMessage />
</FormItem>
</FormField>
</Field>
</FieldGroup>
<!-- Tanggal Daftar (DatePicker) -->
<FieldGroup :column="3">
<Label label-for="register_date">Tanggal Daftar</Label>
<Field id="register_date" :errors="errors">
<FormField v-slot="{ componentField }" name="register_date">
<FormItem>
<FormControl>
<DatepickerSingle v-bind="componentField" placeholder="Pilih tanggal" />
</FormControl>
<FormMessage />
</FormItem>
</FormField>
</Field>
</FieldGroup>
<!-- Jenis Pembayaran (Combobox) -->
<FieldGroup :column="3">
<Label label-for="payment_type">Jenis Pembayaran</Label>
<Field id="payment_type" :errors="errors">
<FormField v-slot="{ componentField }" name="payment_type">
<FormItem>
<FormControl>
<!-- <Combobox id="payment_type" v-bind="componentField" :items="paymentOpts" /> -->
<Select id="payment_type" v-bind="componentField" :items="paymentOpts" />
</FormControl>
<FormMessage />
</FormItem>
</FormField>
</Field>
</FieldGroup>
<FieldGroup :column="3">
<Label label-for="bpjs_number">Kelompok Peserta</Label>
<Field id="bpjs_number" :errors="errors">
<FormField v-slot="{ componentField }" name="bpjs_number">
<FormItem>
<FormControl>
<Input
id="bpjs_number"
v-bind="componentField"
placeholder="Pilih jenis pembayaran terlebih dahulu"
/>
</FormControl>
<FormMessage />
</FormItem>
</FormField>
</Field>
</FieldGroup>
<!-- No. Kartu BPJS -->
<FieldGroup :column="3">
<Label label-for="bpjs_number">No. Kartu BPJS</Label>
<Field id="bpjs_number" :errors="errors">
<FormField v-slot="{ componentField }" name="bpjs_number">
<FormItem>
<FormControl>
<Input
id="bpjs_number"
v-bind="componentField"
placeholder="Pilih jenis pembayaran terlebih dahulu"
/>
</FormControl>
<FormMessage />
</FormItem>
</FormField>
</Field>
</FieldGroup>
<!-- Jenis SEP -->
<FieldGroup :column="3">
<Label label-for="sep_type">Jenis SEP</Label>
<Field id="sep_type" :errors="errors">
<FormField v-slot="{ componentField }" name="sep_type">
<FormItem>
<FormControl>
<Select id="sep_type" v-bind="componentField" :items="sepOpts" />
</FormControl>
<FormMessage />
</FormItem>
</FormField>
</Field>
</FieldGroup>
<!-- No. SEP (input + tombol +) -->
<FieldGroup :column="3">
<Label label-for="sep_number">No. SEP</Label>
<Field id="sep_number" :errors="errors">
<FormField v-slot="{ componentField }" name="sep_number">
<FormItem>
<FormControl>
<div class="flex gap-2">
<Input
id="sep_number"
v-bind="componentField"
placeholder="Tambah SEP terlebih dahulu"
class="flex-1"
/>
<Button class="bg-primary" size="sm" variant="outline" @click.prevent="onAddSep">+</Button>
</div>
</FormControl>
<FormMessage />
</FormItem>
</FormField>
</Field>
</FieldGroup>
<!-- Dokumen SEP (file) -->
<FieldGroup :column="3">
<Label label-for="sep_file">Dokumen SEP</Label>
<Field id="sep_file" :errors="errors">
<FormField v-slot="{ componentField }" name="sep_file">
<FormItem>
<FormControl>
<div class="flex items-center gap-2">
<input ref="sepFileInput" type="file" class="hidden" @change="onSepFileChange" />
<Button class="bg-primary" size="sm" variant="ghost" @click.prevent="pickSepFile"
>Pilih Berkas</Button
>
<Input readonly v-bind="componentField" placeholder="Unggah dokumen SEP" />
</div>
</FormControl>
<FormMessage />
</FormItem>
</FormField>
</Field>
</FieldGroup>
<!-- Dokumen SIPP (file) -->
<FieldGroup :column="3">
<Label label-for="sipp_file">Dokumen SIPP</Label>
<Field id="sipp_file" :errors="errors">
<FormField v-slot="{ componentField }" name="sipp_file">
<FormItem>
<FormControl>
<div class="flex items-center gap-2">
<input ref="sippFileInput" type="file" class="hidden" @change="onSippFileChange" />
<Button class="bg-primary" size="sm" variant="ghost" @click.prevent="pickSippFile"
>Pilih Berkas</Button
>
<Input readonly v-bind="componentField" placeholder="Unggah dokumen SIPP" />
</div>
</FormControl>
<FormMessage />
</FormItem>
</FormField>
</Field>
</FieldGroup>
</Block>
</div>
</div>
</form>
</Form>
</template>
+114
View File
@@ -0,0 +1,114 @@
<script setup lang="ts">
import type { FormErrors } from '~/types/error'
import { toTypedSchema } from '@vee-validate/zod'
import FieldGroup from '~/components/pub/custom-ui/form/field-group.vue'
import Field from '~/components/pub/custom-ui/form/field.vue'
import Label from '~/components/pub/custom-ui/form/label.vue'
import Select from '~/components/pub/custom-ui/form/select.vue'
import { Form } from '~/components/pub/ui/form'
interface InstallationFormData {
name: string
code: string
encounterClassCode: string
}
const props = defineProps<{
installation: {
msg: {
placeholder: string
}
items: {
value: string
label: string
code: string
}[]
}
schema: any
initialValues?: Partial<InstallationFormData>
errors?: FormErrors
}>()
const emit = defineEmits<{
submit: [values: InstallationFormData, resetForm: () => void]
cancel: [resetForm: () => void]
}>()
const formSchema = toTypedSchema(props.schema)
// Form submission handler
function onSubmitForm(values: any, { resetForm }: { resetForm: () => void }) {
const formData: InstallationFormData = {
name: values.name || '',
code: values.code || '',
encounterClassCode: values.encounterClassCode || '',
}
emit('submit', formData, resetForm)
}
// Form cancel handler
function onCancelForm({ resetForm }: { resetForm: () => void }) {
emit('cancel', resetForm)
}
const items = ref([
{ label: 'Rujukan Internal', value: 'ri' },
{ label: 'SEP Rujukan', value: 'sr' },
])
</script>
<template>
<Form
v-slot="{ handleSubmit, resetForm }"
as=""
keep-values
:validation-schema="formSchema"
:initial-values="initialValues"
>
<form id="entry-form" @submit="handleSubmit($event, (values) => onSubmitForm(values, { resetForm }))">
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg xl:text-xl">
<div class="flex flex-col justify-between">
<FieldGroup>
<Label label-for="parentId">Cara Bayar</Label>
<Field id="encounterClassCode" :errors="errors">
<FormField v-slot="{ componentField }" name="encounterClassCode">
<FormItem>
<FormControl>
<Select v-bind="componentField" :items="items" />
</FormControl>
<FormMessage />
</FormItem>
</FormField>
</Field>
</FieldGroup>
<FieldGroup>
<Label label-for="parentId">Poliklinik</Label>
<Field id="encounterClassCode" :errors="errors">
<FormField v-slot="{ componentField }" name="encounterClassCode">
<FormItem>
<FormControl>
<Select v-bind="componentField" :items="items" />
</FormControl>
<FormMessage />
</FormItem>
</FormField>
</Field>
</FieldGroup>
<FieldGroup>
<Label label-for="parentId">Kunjungan</Label>
<Field id="encounterClassCode" :errors="errors">
<FormField v-slot="{ componentField }" name="encounterClassCode">
<FormItem>
<FormControl>
<Select v-bind="componentField" :items="items" />
</FormControl>
<FormMessage />
</FormItem>
</FormField>
</Field>
</FieldGroup>
</div>
</div>
</form>
</Form>
</template>
+131
View File
@@ -0,0 +1,131 @@
import type {
Col,
KeyLabel,
RecComponent,
RecStrFuncComponent,
RecStrFuncUnknown,
Th,
} from '~/components/pub/custom-ui/data/types'
import { defineAsyncComponent } from 'vue'
type SmallDetailDto = any
const action = defineAsyncComponent(() => import('~/components/pub/custom-ui/data/dropdown-action-dud.vue'))
const statusBadge = defineAsyncComponent(() => import('./status-badge.vue'))
export const cols: Col[] = [
{},
{},
{},
{ width: 100 },
{ width: 120 },
{},
{},
{},
{ width: 100 },
{ width: 100 },
{},
{ width: 50 },
]
export const header: Th[][] = [
[
{ label: 'Nama' },
{ label: 'Rekam Medis' },
{ label: 'KTP' },
{ label: 'Tgl Lahir' },
{ label: 'Umur' },
{ label: 'JK' },
{ label: 'Pendidikan' },
{ label: 'Status' },
{ label: '' },
],
]
export const keys = [
'name',
'medicalRecord_number',
'identity_number',
'birth_date',
'patient_age',
'gender',
'education',
'status',
'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 || ''}`
},
identity_number: (rec: unknown): unknown => {
const recX = rec as SmallDetailDto
if (recX.identity_number?.substring(0, 5) === 'BLANK') {
return '(TANPA NIK)'
}
return recX.identity_number
},
birth_date: (rec: unknown): unknown => {
const recX = rec as SmallDetailDto
if (typeof recX.birth_date == 'object' && recX.birth_date) {
return (recX.birth_date as Date).toLocaleDateString()
} else if (typeof recX.birth_date == 'string') {
return (recX.birth_date as string).substring(0, 10)
}
return recX.birth_date
},
patient_age: (rec: unknown): unknown => {
const recX = rec as SmallDetailDto
return recX.birth_date?.split('T')[0]
},
gender: (rec: unknown): unknown => {
const recX = rec as SmallDetailDto
if (typeof recX?.gender_code !== 'number' && recX?.gender_code !== '') {
return 'Tidak Diketahui'
}
return recX.gender_code
},
education: (rec: unknown): unknown => {
const recX = rec as SmallDetailDto
if (typeof recX.education_code == 'number' && recX.education_code >= 0) {
return recX.education_code
} else if (typeof recX.education_code) {
return recX.education_code
}
return '-'
},
}
export const funcComponent: RecStrFuncComponent = {
action(rec, idx) {
const res: RecComponent = {
idx,
rec: rec as object,
component: action,
}
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 = {
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>
@@ -0,0 +1,29 @@
<script setup lang="ts">
import { Badge } from '~/components/pub/ui/badge'
const props = defineProps<{
rec: any
idx?: number
}>()
const doctorStatus = {
0: 'Tidak Aktif',
1: 'Aktif',
}
const statusText = computed(() => {
return doctorStatus[props.rec.status_code as keyof typeof doctorStatus]
})
const badgeVariant = computed(() => {
return props.rec.status_code === 1 ? 'default' : 'destructive'
})
</script>
<template>
<div class="flex justify-center">
<Badge :variant="badgeVariant">
{{ statusText }}
</Badge>
</div>
</template>
@@ -0,0 +1,9 @@
<script setup lang="ts"></script>
<template>
<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> Kunjungan
</div>
<AppEncounterEntryForm />
</template>
+78
View File
@@ -0,0 +1,78 @@
<script setup lang="ts">
import type { DataTableLoader } from '~/components/pub/base/data-table/type'
import type { Summary } from '~/components/pub/base/summary-card/type'
import type { HeaderPrep, RefSearchNav } from '~/components/pub/custom-ui/data/types'
import { Calendar, Hospital, UserCheck, UsersRound } from 'lucide-vue-next'
import SummaryCard from '~/components/pub/base/summary-card/summary-card.vue'
import Dialog from '~/components/pub/base/modal/dialog.vue'
import Header from '~/components/pub/custom-ui/nav-header/prep.vue'
import Filter from '~/components/pub/custom-ui/nav-header/filter.vue'
const data = ref([])
const isLoading = reactive<DataTableLoader>({
summary: false,
isTableLoading: false,
})
const recId = ref<number>(0)
const recAction = ref<string>('')
const recItem = ref<any>(null)
const isFormEntryDialogOpen = ref(false)
const hreaderPrep: HeaderPrep = {
title: 'Kunjungan',
icon: 'i-lucide-users',
addNav: {
label: 'Tambah',
onClick: () => navigateTo('/rehab/encounter/add'),
},
}
const refSearchNav: RefSearchNav = {
onClick: () => {
// open filter modal
isFormEntryDialogOpen.value = true
console.log(' 1open filter modal')
},
onInput: (_val: string) => {
// filter patient list
},
onClear: () => {
// clear url param
},
}
// Loading state management
async function getPatientList() {
isLoading.isTableLoading = true
const resp = await xfetch('/api/v1/patient')
if (resp.success) {
data.value = (resp.body as Record<string, any>).data
}
isLoading.isTableLoading = false
}
onMounted(() => {
getPatientList()
})
provide('rec_id', recId)
provide('rec_action', recAction)
provide('rec_item', recItem)
provide('table_data_loader', isLoading)
</script>
<template>
<Header :prep="{ ...hreaderPrep }" :ref-search-nav="refSearchNav" />
<div class="my-4 rounded-md border p-4">
<Filter :ref-search-nav="refSearchNav" />
<div class="my-4 flex flex-1 flex-col gap-4 md:gap-8">
<AppEncounterList :data="data" />
</div>
<Dialog v-model:open="isFormEntryDialogOpen" title="Tambah Divisi" size="lg" prevent-outside>
<AppEncounterFilter />
</Dialog>
<!-- <Pagination :pagination-meta="paginationMeta" @page-change="handlePageChange" /> -->
</div>
</template>
+23 -31
View File
@@ -112,27 +112,27 @@ const activeTabFilter = computed({
<AppSatusehatCardSummary :is-loading="isLoading.satusehatConn!" :summary-data="summaryData" />
</div>
<div class="rounded-md border p-4">
<h2 class="text-md font-semibold py-2">FHIR Resource</h2>
<h2 class="text-md py-2 font-semibold">FHIR Resource</h2>
<Tabs v-model="activeTabFilter">
<div class="scrollbar-hide overflow-x-auto flex gap-2 justify-between">
<div class="scrollbar-hide flex justify-between gap-2 overflow-x-auto">
<TabsList>
<TabsTrigger
v-for="tab in tabs" :key="tab.value" :value="tab.value"
v-for="tab in tabs"
:key="tab.value"
:value="tab.value"
class="flex-shrink-0 px-4 py-2 text-sm font-medium data-[state=active]:bg-green-600 data-[state=inactive]:bg-gray-100 data-[state=active]:text-white data-[state=inactive]:text-gray-700"
>
>
{{ tab.label }}
</TabsTrigger>
</TabsList>
<div class="flex gap-2 items-center">
<div class="flex items-center gap-2">
<!-- Search Input -->
<AppSatusehatPicker />
<div class="relative w-full max-w-sm">
<Dialog>
<DialogTrigger>
<Input type="text" placeholder="Cari pasien..." class="pl-3 h-9" />
<Input type="text" placeholder="Cari pasien..." class="h-9 pl-3" />
</DialogTrigger>
<DialogContent>
<DialogHeader class="border-b-1">
@@ -152,19 +152,13 @@ v-for="tab in tabs" :key="tab.value" :value="tab.value"
<FormLabel>Status</FormLabel>
<FormControl>
<Select v-bind="componentField">
<SelectTrigger class="bg-white border border-gray-300">
<SelectTrigger class="border border-gray-300 bg-white">
<SelectValue class="text-gray-400" placeholder="-- select item" />
</SelectTrigger>
<SelectContent class="bg-white ">
<SelectItem value="0">
Gagal
</SelectItem>
<SelectItem value="1">
Pending
</SelectItem>
<SelectItem value="2">
Terkirim
</SelectItem>
<SelectContent class="bg-white">
<SelectItem value="0"> Gagal </SelectItem>
<SelectItem value="1"> Pending </SelectItem>
<SelectItem value="2"> Terkirim </SelectItem>
</SelectContent>
</Select>
</FormControl>
@@ -181,12 +175,8 @@ v-for="tab in tabs" :key="tab.value" :value="tab.value"
</Form>
<DialogFooter>
<Button variant="outline">
Reset
</Button>
<Button>
Apply
</Button>
<Button variant="outline"> Reset </Button>
<Button> Apply </Button>
</DialogFooter>
</DialogContent>
</Dialog>
@@ -194,9 +184,11 @@ v-for="tab in tabs" :key="tab.value" :value="tab.value"
<!-- Action Buttons -->
<AppSatusehatButtonAction
v-for="action in actions" :key="action.value" :icon="action.icon"
v-for="action in actions"
:key="action.value"
:icon="action.icon"
:text="action.label"
/>
/>
</div>
</div>
<div class="mt-4">
@@ -206,11 +198,11 @@ v-for="action in actions" :key="action.value" :icon="action.icon"
<!-- Pagination -->
<div
v-if="!isLoading.satusehatConn && !isLoading.isTableLoading && pagination.total > 0"
class="mt-4 flex justify-between items-center"
>
v-if="!isLoading.satusehatConn && !isLoading.isTableLoading && pagination.total > 0"
class="mt-4 flex items-center justify-between"
>
<div class="text-sm text-muted-foreground">
Menampilkan {{ ((pagination.page - 1) * pagination.limit) + 1 }} -
Menampilkan {{ (pagination.page - 1) * pagination.limit + 1 }} -
{{ Math.min(pagination.page * pagination.limit, pagination.total) }}
dari {{ pagination.total }} data
</div>
@@ -0,0 +1,9 @@
<script setup lang="ts">
definePageMeta({
roles: ['sys', 'doc'],
})
</script>
<template>
<div>detail pasien</div>
</template>
@@ -0,0 +1,9 @@
<script setup lang="ts">
definePageMeta({
roles: ['sys', 'doc'],
})
</script>
<template>
<div>edit pasien</div>
</template>
@@ -0,0 +1,41 @@
<script setup lang="ts">
import type { PagePermission } from '~/models/role'
import Error from '~/components/pub/base/error/error.vue'
import { PAGE_PERMISSIONS } from '~/lib/page-permission'
definePageMeta({
middleware: ['rbac'],
roles: ['doctor', 'nurse', 'admisi', 'pharmacy', 'billing', 'management'],
title: 'Tambah Kunjungan',
contentFrame: 'cf-full-width',
})
const route = useRoute()
useHead({
title: () => route.meta.title,
})
const roleAccess: PagePermission = PAGE_PERMISSIONS['/rehab/encounter']
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',
})
}
// Define permission-based computed properties
const canCreate = hasCreateAccess(roleAccess)
</script>
<template>
<div v-if="canCreate">
<ContentEncounterEntry />
</div>
<Error v-else :status-code="403" />
</template>
@@ -0,0 +1,40 @@
<script setup lang="ts">
import type { PagePermission } from '~/models/role'
import Error from '~/components/pub/base/error/error.vue'
import { PAGE_PERMISSIONS } from '~/lib/page-permission'
definePageMeta({
middleware: ['rbac'],
roles: ['doctor', 'nurse', 'admisi', 'pharmacy', 'billing', 'management'],
title: 'Daftar Kunjungan',
contentFrame: 'cf-full-width',
})
const route = useRoute()
useHead({
title: () => route.meta.title as string,
})
const roleAccess: PagePermission = PAGE_PERMISSIONS['/rehab/encounter']
const { checkRole, hasReadAccess } = useRBAC()
// Check if user has access to this page
const hasAccess = checkRole(roleAccess)
if (!hasAccess) {
navigateTo('/403')
}
// Define permission-based computed properties
const canRead = hasReadAccess(roleAccess)
</script>
<template>
<div>
<div v-if="canRead">
<ContentEncounterList />
</div>
<Error v-else :status-code="403" />
</div>
</template>