Merge pull request #191 from dikstub-rssa/feat/page-cleaning

Feat/page cleaning
This commit is contained in:
Munawwirul Jamal
2025-11-28 19:20:57 +07:00
committed by Andrian Roshandy
56 changed files with 998 additions and 655 deletions
@@ -9,38 +9,38 @@ const props = defineProps<{
const recId = inject<Ref<number>>('rec_id')!
const recAction = inject<Ref<string>>('rec_action')!
const recItem = inject<Ref<any>>('rec_item')!
const activeServicePosition = inject<Ref<string>>('activeServicePosition')! // previously: activePosition
const activeKey = ref<string | null>(null)
const activePosition = inject<Ref<string>>('position')!
const linkItemsFiltered = ref<LinkItem[]>([])
const linkItemsBase: LinkItem[] = [
{
label: 'Nothing',
value: 'nothing',
icon: 'i-lucide-file',
},
]
const linkItems: LinkItem[] = [
const baseLinkItems: LinkItem[] = [
{
label: 'Detail',
value: 'detail',
onClick: () => {
detail()
proceedItem(ActionEvents.showDetail)
},
icon: 'i-lucide-eye',
},
]
const medicalLinkItems: LinkItem[] = [
{
label: 'Process',
value: 'edit',
value: 'process',
onClick: () => {
edit()
proceedItem(ActionEvents.showProcess)
},
icon: 'i-lucide-pencil',
icon: 'i-lucide-shuffle',
},
]
const regLinkItems: LinkItem[] = [
{
label: 'Print',
value: 'print',
onClick: () => {
print()
proceedItem(ActionEvents.showPrint)
},
icon: 'i-lucide-printer',
},
@@ -48,7 +48,7 @@ const linkItems: LinkItem[] = [
label: 'Batalkan',
value: 'cancel',
onClick: () => {
cancel()
proceedItem(ActionEvents.showCancel)
},
icon: 'i-lucide-circle-x',
},
@@ -56,65 +56,49 @@ const linkItems: LinkItem[] = [
label: 'Hapus',
value: 'remove',
onClick: () => {
remove()
proceedItem(ActionEvents.showConfirmDelete)
},
icon: 'i-lucide-trash',
},
]
function detail() {
const voidLinkItems: LinkItem[] = [
{
label: 'Nothing',
value: 'nothing',
icon: 'i-lucide-file',
},
]
linkItemsFiltered.value = [...baseLinkItems]
getLinks()
watch(activeServicePosition, () => {
getLinks()
})
function proceedItem(action: string) {
recId.value = props.rec.id || 0
recAction.value = ActionEvents.showDetail
recItem.value = props.rec
recAction.value = action
}
function edit() {
recId.value = props.rec.id || 0
recAction.value = ActionEvents.showEdit
recItem.value = props.rec
}
function print() {
recId.value = props.rec.id || 0
recAction.value = ActionEvents.showPrint
recItem.value = props.rec
}
function cancel() {
recId.value = props.rec.id || 0
recAction.value = ActionEvents.showCancel
recItem.value = props.rec
}
function remove() {
recId.value = props.rec.id || 0
recAction.value = ActionEvents.showConfirmDelete
recItem.value = props.rec
}
linkItemsFiltered.value = [...linkItemsBase]
function getLinks(position: string) {
switch (position) {
function getLinks() {
switch (activeServicePosition.value) {
case 'medical':
linkItemsFiltered.value = [...linkItems]
linkItemsFiltered.value = [...baseLinkItems, ...medicalLinkItems]
break
case 'verificator':
linkItemsFiltered.value = [
...linkItems.filter((item) => ['detail', 'print'].includes(item.value || '')),
]
case 'registration':
linkItemsFiltered.value = [...baseLinkItems, ...regLinkItems]
case 'unit|resp':
linkItemsFiltered.value = [...baseLinkItems]
break
default:
linkItemsFiltered.value = [...linkItemsBase]
linkItemsFiltered.value = voidLinkItems
break
}
}
getLinks(activePosition.value)
watch(activePosition, () => {
getLinks(activePosition.value)
})
</script>
<template>
+123
View File
@@ -0,0 +1,123 @@
<script setup lang="ts">
import { Calendar as CalendarIcon, Filter as FilterIcon, Search } from 'lucide-vue-next'
import { ref } from 'vue'
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 { RefExportNav, RefSearchNav } from '~/components/pub/my-ui/data/types'
const props = defineProps<{
refSearchNav?: RefSearchNav
enableExport?: boolean
refExportNav?: RefExportNav
onFilterClick?: () => void
onExportPdf?: () => void
onExportExcel?: () => void
onExportCsv?: () => void
}>()
// function emitSearchNavClick() {
// props.refSearchNav?.onClick()
// }
//
// function onInput(event: Event) {
// props.refSearchNav?.onInput((event.target as HTMLInputElement).value)
// }
//
// function btnClick() {
// props.prep?.addNav?.onClick?.()
// }
const searchQuery = ref('')
const dateRange = ref<{ from: Date | null; to: Date | null }>({
from: new Date(),
to: new Date(),
})
const df = new DateFormatter('en-US', {
dateStyle: 'medium',
})
// Get current date
const today = new Date()
const todayCalendar = new CalendarDate(today.getFullYear(), today.getMonth() + 1, today.getDate())
// Get date 1 month ago
const oneMonthAgo = new Date(today)
oneMonthAgo.setMonth(today.getMonth() - 1)
const oneMonthAgoCalendar = new CalendarDate(oneMonthAgo.getFullYear(), oneMonthAgo.getMonth() + 1, oneMonthAgo.getDate())
const value = ref({
start: oneMonthAgoCalendar,
end: todayCalendar,
}) as Ref<DateRange>
// function onFilterClick() {
// console.log('Search:', searchQuery.value)
// console.log('Date Range:', dateRange.value)
// props.refSearchNav?.onClick()
// }
</script>
<template>
<div class="relative w-64">
<Search class="absolute left-3 top-1/2 size-4 -translate-y-1/2 text-gray-400" />
<Input v-model="searchQuery" type="text" placeholder="Cari Nama /No.RM" class="pl-9" />
</div>
<Popover>
<PopoverTrigger as-child>
<Button
variant="outline"
:class="cn('w-[200px] justify-start text-left font-normal', !value && 'text-muted-foreground')"
>
<CalendarIcon class="mr-2 h-4 w-4" />
<template v-if="value.start">
<template v-if="value.end">
{{ df.format(value.start.toDate(getLocalTimeZone())) }} -
{{ df.format(value.end.toDate(getLocalTimeZone())) }}
</template>
<template v-else>
{{ df.format(value.start.toDate(getLocalTimeZone())) }}
</template>
</template>
<template v-else> Pick a date </template>
</Button>
</PopoverTrigger>
<PopoverContent class="w-auto p-0">
<RangeCalendar
v-model="value"
initial-focus
:number-of-months="2"
@update:start-value="(startDate) => (value.start = startDate)"
/>
</PopoverContent>
</Popover>
<Button variant="outline" class="border-orange-500 text-orange-600 hover:bg-orange-50" @click="onFilterClick">
<FilterIcon class="mr-2 size-4" />
Filter
</Button>
<DropdownMenu v-show="props.enableExport">
<DropdownMenuTrigger as-child>
<Button variant="outline" class="ml-auto border-orange-500 text-orange-600 hover:bg-orange-50">
<Icon name="i-lucide-download" class="h-4 w-4" />
Ekspor
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem @click="onExportPdf">
Ekspor PDF
</DropdownMenuItem>
<DropdownMenuItem @click="onExportCsv">
Ekspor CSV
</DropdownMenuItem>
<DropdownMenuItem @click="onExportExcel">
Ekspor Excel
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</template>
+2 -1
View File
@@ -1,4 +1,5 @@
<script setup lang="ts">
import DataTable from '~/components/pub/my-ui/data-table/data-table.vue'
import { config } from './list.cfg'
const props = defineProps<{
@@ -7,7 +8,7 @@ const props = defineProps<{
</script>
<template>
<PubMyUiDataTable
<DataTable
v-bind="config"
:rows="props.data"
/>
+30 -25
View File
@@ -1,52 +1,52 @@
<script setup lang="ts">
import * as DE from '~/components/pub/my-ui/doc-entry'
import { genderCodes } from '~/const/key-val/person';
import type { Encounter } from '~/models/encounter'
const props = defineProps<{
data: Encounter
}>()
let address = ''
let address = ref('')
if (props.data.patient.person.addresses) {
address = props.data.patient.person.addresses.map((a) => a.address).join(', ')
address.value = props.data.patient.person.addresses.map((a) => a.address).join(', ')
}
let dpjp = ''
let dpjp = ref('')
if (props.data.responsible_doctor) {
const dp = props.data.responsible_doctor.employee.person
dpjp = `${dp.frontTitle} ${dp.name} ${dp.endTitle}`
dpjp.value = `${dp.frontTitle} ${dp.name} ${dp.endTitle}`
} else if (props.data.appointment_doctor) {
dpjp = props.data.appointment_doctor.employee.person.name
dpjp.value = props.data.appointment_doctor.employee.person.name
}
</script>
<template>
<div class="w-full rounded-md border bg-white p-4 shadow-sm dark:bg-neutral-950">
<div class="w-full">
<!-- Data Pasien -->
<h2 class="mb-2 font-semibold md:text-base 2xl:text-lg">
{{ data.patient.person.name }} - {{ data.patient.number }}
</h2>
<div class="grid grid-cols-3">
<div>
<DE.Block
mode="preview"
labelSize="large"
>
<DE.Block mode="preview">
<DE.Cell>
<DE.Label class="font-semibold">No. RM</DE.Label>
<DE.Colon />
<DE.Field>
{{ data.patient.person.birthDate?.substring(0, 10) }}
</DE.Field>
</DE.Cell>
<DE.Cell>
<DE.Label class="font-semibold">Jenis Kelamin</DE.Label>
<DE.Label class="font-semibold">Jns. Kelamin</DE.Label>
<DE.Colon />
<DE.Field>
{{ data.patient.person.gender_code }}
{{ genderCodes[data.patient.person.gender_code as keyof typeof genderCodes] }}
</DE.Field>
</DE.Cell>
<DE.Cell>
<DE.Label class="font-semibold">Alamat</DE.Label>
<DE.Colon />
<DE.Field>
<div v-html="address"></div>
</DE.Field>
@@ -54,35 +54,39 @@ if (props.data.responsible_doctor) {
</DE.Block>
</div>
<div>
<DE.Block
mode="preview"
labelSize="large"
>
<DE.Block mode="preview">
<DE.Cell>
<DE.Label class="font-semibold">Tgl. Kunjungan</DE.Label>
<DE.Label position="dynamic" class="font-semibold">Tgl. Masuk</DE.Label>
<DE.Colon />
<DE.Field>
{{ data.visitDate.substring(0, 10) }}
</DE.Field>
</DE.Cell>
<DE.Cell>
<DE.Label class="font-semibold">Klinik</DE.Label>
<DE.Label position="dynamic" class="font-semibold">Poliklinik</DE.Label>
<DE.Colon />
<DE.Field>
{{ data.unit?.name }}
</DE.Field>
</DE.Cell>
<DE.Cell>
<DE.Label class="font-semibold">DPJP</DE.Label>
<DE.Label position="dynamic" class="font-semibold">Klinik</DE.Label>
<DE.Colon />
<DE.Field>
{{ dpjp }}
{{ data.unit?.name }}
</DE.Field>
</DE.Cell>
</DE.Block>
</div>
<div>
<DE.Block
mode="preview"
labelSize="large"
>
<DE.Block mode="preview">
<DE.Cell>
<DE.Label position="dynamic" class="font-semibold">DPJP</DE.Label>
<DE.Colon />
<DE.Field>
{{ dpjp }}
</DE.Field>
</DE.Cell>
<DE.Cell>
<DE.Label
position="dynamic"
@@ -90,6 +94,7 @@ if (props.data.responsible_doctor) {
>
Billing
</DE.Label>
<DE.Colon class="pt-1" />
<DE.Field class="text-base 2xl:text-lg">
Rp. 000.000
<!-- {{ data }} -->
+113 -146
View File
@@ -1,41 +1,40 @@
<script setup lang="ts">
// Components
import { Calendar, Hospital, UserCheck, UsersRound } from 'lucide-vue-next'
import SummaryCard from '~/components/pub/my-ui/summary-card/summary-card.vue'
///// Imports
// Pub components
import type { DataTableLoader } from '~/components/pub/my-ui/data-table/type'
import { toast } from '~/components/pub/ui/toast'
import { ActionEvents } from '~/components/pub/my-ui/data/types'
import Dialog from '~/components/pub/my-ui/modal/dialog.vue'
import Header from '~/components/pub/my-ui/nav-header/prep.vue'
import Filter from '~/components/pub/my-ui/nav-header/filter.vue'
import * as CH from '~/components/pub/my-ui/content-header'
import RecordConfirmation from '~/components/pub/my-ui/confirmation/record-confirmation.vue'
import { useSidebar } from '~/components/pub/ui/sidebar/utils'
// Libs
import { getPositionAs } from '~/lib/roles'
// Types
import type { DataTableLoader } from '~/components/pub/my-ui/data-table/type'
import type { Summary } from '~/components/pub/my-ui/summary-card/type'
import type { HeaderPrep, RefSearchNav } from '~/components/pub/my-ui/data/types'
import { ActionEvents } from '~/components/pub/my-ui/data/types'
// App libs
import { getServicePosition } from '~/lib/roles' // previously getPositionAs
// Services
import { getList as getEncounterList, remove as removeEncounter, cancel as cancelEncounter } from '~/services/encounter.service'
// UI
import { toast } from '~/components/pub/ui/toast'
// Apps
import Content from '~/components/app/encounter/list.vue'
import FilterNav from '~/components/app/encounter/filter-nav.vue'
import FilterForm from '~/components/app/encounter/filter-form.vue'
// Props
const props = defineProps<{
classCode?: 'ambulatory' | 'emergency' | 'inpatient'
subClassCode?: 'reg' | 'rehab' | 'chemo' | 'emg' | 'eon' | 'op' | 'icu' | 'hcu' | 'vk'
canCreate?: boolean
canUpdate?: boolean
canDelete?: boolean
}>()
///// Declarations and Flows
// Sidebar automation
const { setOpen } = useSidebar()
setOpen(true)
const { getActiveRole } = useUserStore()
const activeRole = getActiveRole()
const activePosition = ref(getPositionAs(activeRole))
const props = defineProps<{
classCode?: 'ambulatory' | 'emergency' | 'inpatient' | 'outpatient'
subClassCode?: 'reg' | 'rehab' | 'chemo' | 'emg' | 'eon' | 'op' | 'icu' | 'hcu' | 'vk'
type: string
}>()
// Main data
const data = ref([])
const isLoading = reactive<DataTableLoader>({
summary: false,
@@ -44,67 +43,88 @@ const isLoading = reactive<DataTableLoader>({
const recId = ref<number>(0)
const recAction = ref<string>('')
const recItem = ref<any>(null)
const isFormEntryDialogOpen = ref(false)
const isFilterFormDialogOpen = ref(false)
const isRecordConfirmationOpen = ref(false)
const isRecordCancelOpen = ref(false)
const hreaderPrep: HeaderPrep = {
// Headers
const hreaderPrep: CH.Config = {
title: 'Kunjungan',
icon: 'i-lucide-users',
addNav: {
label: 'Tambah',
onClick: () => {
if (props.classCode === 'ambulatory' && props.subClassCode === 'rehab') {
navigateTo('/rehab/encounter/add')
}
if (props.classCode === 'ambulatory' && props.subClassCode === 'reg') {
navigateTo('/outpatient/encounter/add')
}
if (props.classCode === 'emergency') {
navigateTo('/emergency/encounter/add')
}
if (props.classCode === 'inpatient') {
navigateTo('/inpatient/encounter/add')
}
navigateTo(`/${props.classCode}/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
},
if (!props.canCreate) {
delete hreaderPrep.addNav
}
// Loading state management
// Filters
const filter = ref<{
installation: {
msg: {
placeholder: string
}
items: {
value: string
label: string
code: string
}[]
}
schema: any
initialValues?: Partial<any>
errors?: any
}>({
installation: {
msg: {
placeholder: 'Pilih',
},
items: [],
},
schema: {},
})
/**
* Get base path for encounter routes based on classCode and subClassCode
*/
function getBasePath(): string {
if (props.classCode === 'ambulatory' && props.subClassCode === 'rehab') {
return '/rehab/encounter'
}
if (props.classCode === 'ambulatory' && props.subClassCode === 'reg') {
return '/outpatient/encounter'
}
if (props.classCode === 'emergency') {
return '/emergency/encounter'
}
if (props.classCode === 'inpatient') {
return '/inpatient/encounter'
}
return '/encounter' // fallback
}
// Role reactivities
const { getActiveRole } = useUserStore()
const activeServicePosition = ref(getServicePosition(getActiveRole()))
provide('activeServicePosition', activeServicePosition)
watch(getActiveRole, (role? : string) => {
activeServicePosition.value = getServicePosition(role)
})
// Recrod reactivities
provide('rec_id', recId)
provide('rec_action', recAction)
provide('rec_item', recItem)
provide('table_data_loader', isLoading)
watch(() => recAction.value, () => {
const basePath = `/${props.classCode}/encounter`
// console.log(`${basePath}/${recId.value}`, recAction.value)
// return
if (recAction.value === ActionEvents.showConfirmDelete) {
isRecordConfirmationOpen.value = true
} else if (recAction.value === ActionEvents.showCancel) {
isRecordCancelOpen.value = true
} else if (recAction.value === ActionEvents.showDetail) {
navigateTo(`${basePath}/${recId.value}`)
} else if (recAction.value === ActionEvents.showEdit) {
navigateTo(`${basePath}/${recId.value}/edit`)
} else if (recAction.value === ActionEvents.showProcess) {
navigateTo(`${basePath}/${recId.value}/process`)
} else if (recAction.value === ActionEvents.showConfirmDelete) {
isRecordConfirmationOpen.value = true
}
recAction.value = '' // reset
})
onMounted(() => {
getPatientList()
})
/////// Functions
async function getPatientList() {
isLoading.isTableLoading = true
try {
@@ -215,94 +235,32 @@ function handleRemoveConfirmation() {
recItem.value = null
isRecordConfirmationOpen.value = false
}
watch(
() => recAction.value,
() => {
if (recAction.value === ActionEvents.showConfirmDelete) {
isRecordConfirmationOpen.value = true
return
}
if (recAction.value === ActionEvents.showCancel) {
isRecordCancelOpen.value = true
return
}
const basePath = getBasePath()
if (props.type === 'encounter') {
if (recAction.value === 'showDetail') {
navigateTo(`${basePath}/${recId.value}/detail`)
} else if (recAction.value === 'showEdit') {
navigateTo(`${basePath}/${recId.value}/process`)
} else if (recAction.value === 'showPrint') {
console.log('print')
} else {
// handle other actions
}
} else if (props.type === 'registration') {
// Handle registration type if needed
if (recAction.value === 'showDetail') {
navigateTo(`${basePath.replace('/encounter', '/registration')}/${recId.value}/detail`)
} else if (recAction.value === 'showEdit') {
navigateTo(`${basePath.replace('/encounter', '/registration')}/${recId.value}/edit`)
} else if (recAction.value === 'showProcess') {
navigateTo(`${basePath.replace('/encounter', '/registration')}/${recId.value}/process`)
} else {
// handle other actions
}
}
},
)
watch(getActiveRole, () => {
const activeRole = getActiveRole()
activePosition.value = getPositionAs(activeRole)
})
provide('rec_id', recId)
provide('rec_action', recAction)
provide('rec_item', recItem)
provide('table_data_loader', isLoading)
provide('position', activePosition)
onMounted(() => {
getPatientList()
})
</script>
<template>
<Header
:prep="{ ...hreaderPrep }"
:ref-search-nav="refSearchNav"
/>
<Separator class="my-4 xl:my-5" />
<CH.ContentHeader v-bind="hreaderPrep">
<FilterNav
@onFilterClick="() => isFilterFormDialogOpen = true"
@onExportPdf="() => {}"
@onExportExcel="() => {}"
@nExportCsv="() => {}"
/>
</CH.ContentHeader>
<Filter
:prep="hreaderPrep"
:ref-search-nav="refSearchNav"
/>
<AppEncounterList :data="data" />
<Content :data="data" />
<!-- Filter -->
<Dialog
v-model:open="isFormEntryDialogOpen"
v-model:open="isFilterFormDialogOpen"
title="Filter"
size="lg"
prevent-outside
>
<AppEncounterFilter
:installation="{
msg: { placeholder: 'Pilih' },
items: [],
}"
:schema="{}"
/>
<FilterForm v-bind="filter" />
</Dialog>
<!-- Record Confirmation Modal -->
<RecordConfirmation
<!-- Batal -->
<RecordConfirmation
v-model:open="isRecordCancelOpen"
custom-title="Batalkan Kunjungan"
custom-message="Apakah anda yakin ingin membatalkan kunjungan pasien berikut?"
@@ -325,7 +283,9 @@ onMounted(() => {
</template>
</RecordConfirmation>
<!-- Hapus -->
<RecordConfirmation
v-if="canDelete"
v-model:open="isRecordConfirmationOpen"
action="delete"
:record="recItem"
@@ -349,4 +309,11 @@ onMounted(() => {
</div>
</template>
</RecordConfirmation>
<Dialog
title="Hapus data"
size="md"
v-model:open="isRecordConfirmationOpen"
>
Hak akses tidak memenuhi kriteria untuk proses ini.
</Dialog>
</template>
@@ -0,0 +1,114 @@
<script setup lang="ts">
//
import { computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { getDetail } from '~/services/encounter.service'
//
import type { TabItem } from '~/components/pub/my-ui/comp-tab/type'
import CompTab from '~/components/pub/my-ui/comp-tab/comp-tab.vue'
import { genEncounter } from '~/models/encounter'
// PLASE ORDER BY TAB POSITION
import Status from '~/components/content/encounter/status.vue'
import AssesmentFunctionList from '~/components/content/soapi/entry.vue'
import EarlyMedicalAssesmentList from '~/components/content/soapi/entry.vue'
import EarlyMedicalRehabList from '~/components/content/soapi/entry.vue'
import DeviceOrder from '~/components/content/device-order/main.vue'
import Prescription from '~/components/content/prescription/main.vue'
import CpLabOrder from '~/components/content/cp-lab-order/main.vue'
import Radiology from '~/components/content/radiology-order/main.vue'
import Consultation from '~/components/content/consultation/list.vue'
import Cprj from '~/components/content/cprj/entry.vue'
import DocUploadList from '~/components/content/document-upload/list.vue'
import GeneralConsentList from '~/components/content/general-consent/entry.vue'
import ResumeList from '~/components/content/resume/list.vue'
import ControlLetterList from '~/components/content/control-letter/list.vue'
const route = useRoute()
const router = useRouter()
// activeTab selalu sinkron dengan query param
const activeTab = computed({
get: () => (route.query?.tab && typeof route.query.tab === 'string' ? route.query.tab : 'status'),
set: (val: string) => {
router.replace({ path: route.path, query: { tab: val } })
},
})
const id = typeof route.params.id == 'string' ? parseInt(route.params.id) : 0
const data = ref(genEncounter())
async function fetchDetail() {
const res = await getDetail(id, {
includes:
'patient,patient-person,patient-person-addresses,unit,Appointment_Doctor,Appointment_Doctor-employee,Appointment_Doctor-employee-person,EncounterDocuments',
})
if (res.body?.data) data.value = res.body?.data
}
onMounted(() => {
fetchDetail()
})
const tabs: TabItem[] = [
{ value: 'status', label: 'Status Masuk/Keluar', component: Status, props: { encounter: data } },
{
value: 'early-medical-assessment',
label: 'Pengkajian Awal Medis',
component: EarlyMedicalAssesmentList,
props: { encounter: data, type: 'early-medic', label: 'Pengkajian Awal Medis' },
},
{
value: 'rehab-medical-assessment',
label: 'Pengkajian Awal Medis Rehabilitasi Medis',
component: EarlyMedicalRehabList,
props: { encounter: data, type: 'early-rehab', label: 'Pengkajian Awal Medis Rehabilitasi Medis' },
},
{
value: 'function-assessment',
label: 'Asesmen Fungsi',
component: AssesmentFunctionList,
props: { encounter: data, type: 'function', label: 'Asesmen Fungsi' },
},
{ value: 'therapy-protocol', label: 'Protokol Terapi' },
{ value: 'education-assessment', label: 'Asesmen Kebutuhan Edukasi' },
{ value: 'patient-note', label: 'CPRJ', component: Cprj, props: { encounter: data } },
{ value: 'consent', label: 'General Consent', component: GeneralConsentList, props: { encounter: data } },
{ value: 'prescription', label: 'Order Obat', component: Prescription, props: { encounter_id: data.value.id } },
{ value: 'device-order', label: 'Order Alkes', component: DeviceOrder, props: { encounter_id: data.value.id } },
{ value: 'device', label: 'Order Alkes' },
{ value: 'mcu-radiology', label: 'Order Radiologi', component: Radiology, props: { encounter_id: data.id } },
{ value: 'mcu-lab-cp', label: 'Order Lab PK', component: CpLabOrder, props: { encounter_id: data.id } },
{ value: 'mcu-lab-micro', label: 'Order Lab Mikro' },
{ value: 'mcu-lab-pa', label: 'Order Lab PA' },
{ value: 'medical-action', label: 'Order Ruang Tindakan' },
{ value: 'mcu-result', label: 'Hasil Penunjang' },
{ value: 'consultation', label: 'Konsultasi', component: Consultation, props: { encounter: data } },
{ value: 'resume', label: 'Resume', component: ResumeList, props: { encounter: data } },
{ 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 },
},
]
</script>
<template>
<div class="w-full">
<div class="mb-4">
<PubMyUiNavContentBa label="Kembali ke Daftar Kunjungan" />
</div>
<AppEncounterQuickInfo :data="data" />
<CompTab
:data="tabs"
:initial-active-tab="activeTab"
@change-tab="activeTab = $event"
/>
</div>
</template>
@@ -1,132 +0,0 @@
<script setup lang="ts">
import { computed } from 'vue'
// Pubs components
import ContentSwitcher from '~/components/pub/my-ui/content-switcher/content-switcher.vue'
import { useSidebar } from '~/components/pub/ui/sidebar/utils'
// Components
import EncounterPatientInfo from '~/components/app/encounter/quick-info-full.vue'
import EncounterHistoryButtonMenu from '~/components/app/encounter/quick-shortcut.vue'
import SubMenu from '~/components/pub/my-ui/menus/submenu.vue'
// Libraries
import { getPositionAs } from '~/lib/roles'
// Models
import { genEncounter } from '~/models/encounter'
// Types
import type { EncounterProps } from '~/handlers/encounter-init.handler'
// Handlers
import { getEncounterData } from '~/handlers/encounter-process.handler'
import { getMenuItems } from "~/handlers/encounter-init.handler"
const { user, getActiveRole } = useUserStore()
const route = useRoute()
const router = useRouter()
const props = defineProps<{
classCode?: EncounterProps['classCode']
subClassCode?: EncounterProps['subClassCode']
}>()
const activeRole = getActiveRole()
const activePosition = ref(getPositionAs(activeRole))
const menus = ref([] as any)
const activeMenu = computed({
get: () => (route.query?.menu && typeof route.query.menu === 'string' ? route.query.menu : 'status'),
set: (value: string) => {
router.replace({ path: route.path, query: { menu: value } })
},
})
const id = typeof route.params.id == 'string' ? parseInt(route.params.id) : 0
const data = ref<any>(genEncounter())
const isShowPatient = computed(() => data.value && data.value?.patient?.person)
const { setOpen } = useSidebar()
setOpen(false)
if (activePosition.value === 'none') { // if user position is none, redirect to home page
router.push('/')
}
// Dummy rows for ProtocolList (matches keys expected by list-cfg.protocol)
const protocolRows = [
{
number: '1',
tanggal: new Date().toISOString().substring(0, 10),
siklus: 'I',
periode: 'Siklus I',
kehadiran: 'Hadir',
action: '',
},
{
number: '2',
tanggal: new Date().toISOString().substring(0, 10),
siklus: 'II',
periode: 'Siklus II',
kehadiran: 'Tidak Hadir',
action: '',
},
]
const paginationMeta = {
recordCount: protocolRows.length,
page: 1,
pageSize: 10,
totalPage: 1,
hasNext: false,
hasPrev: false,
}
function handleClick(type: string) {
if (type === 'draft') {
router.back()
}
}
function initMenus() {
menus.value = getMenuItems(id, props, user, {
encounter: data.value
} as any, {
protocolTheraphy: paginationMeta,
protocolChemotherapy: paginationMeta,
medicineProtocolChemotherapy: paginationMeta,
})
}
async function getData() {
data.value = await getEncounterData(id)
}
watch(getActiveRole, () => {
const activeRole = getActiveRole()
activePosition.value = getPositionAs(activeRole)
initMenus()
})
onMounted(async () => {
await getData()
initMenus()
})
</script>
<template>
<div class="w-full">
<div class="mb-4">
<PubMyUiNavContentBa label="Kembali ke Daftar Kunjungan" @click="handleClick" />
</div>
<ContentSwitcher :active="1" :height="200">
<template v-slot:content1>
<EncounterPatientInfo v-if="isShowPatient" :data="data" />
</template>
<template v-slot:content2>
<EncounterHistoryButtonMenu v-if="isShowPatient" />
</template>
</ContentSwitcher>
<SubMenu :data="menus" :initial-active-menu="activeMenu" @change-menu="activeMenu = $event" />
</div>
</template>
+113 -85
View File
@@ -1,114 +1,142 @@
<script setup lang="ts">
//
import { computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
///// Imports
// Pubs components
import ContentSwitcher from '~/components/pub/my-ui/content-switcher/content-switcher.vue'
import { useSidebar } from '~/components/pub/ui/sidebar/utils'
import SubMenu from '~/components/pub/my-ui/menus/submenu.vue'
import ContentNavBa from '~/components/pub/my-ui/nav-content/ba.vue'
import { getDetail } from '~/services/encounter.service'
// App libs
import { getServicePosition } from '~/lib/roles' // previously getPositionAs
//
import type { TabItem } from '~/components/pub/my-ui/comp-tab/type'
import CompTab from '~/components/pub/my-ui/comp-tab/comp-tab.vue'
// App Models
import { genEncounter, type Encounter } from '~/models/encounter'
import { genEncounter } from '~/models/encounter'
// Handlers
import type { EncounterProps } from '~/handlers/encounter-init.handler'
import { getEncounterData } from '~/handlers/encounter-process.handler'
import { getMenuItems } from "~/handlers/encounter-init.handler"
// PLASE ORDER BY TAB POSITION
import Status from '~/components/content/encounter/status.vue'
import AssesmentFunctionList from '~/components/content/soapi/entry.vue'
import EarlyMedicalAssesmentList from '~/components/content/soapi/entry.vue'
import EarlyMedicalRehabList from '~/components/content/soapi/entry.vue'
import DeviceOrder from '~/components/content/device-order/main.vue'
import Prescription from '~/components/content/prescription/main.vue'
import CpLabOrder from '~/components/content/cp-lab-order/main.vue'
import Radiology from '~/components/content/radiology-order/main.vue'
import Consultation from '~/components/content/consultation/list.vue'
import Cprj from '~/components/content/cprj/entry.vue'
import DocUploadList from '~/components/content/document-upload/list.vue'
import GeneralConsentList from '~/components/content/general-consent/entry.vue'
import ResumeList from '~/components/content/resume/list.vue'
import ControlLetterList from '~/components/content/control-letter/list.vue'
// App Components
import EncounterPatientInfo from '~/components/app/encounter/quick-info.vue'
import EncounterHistoryButtonMenu from '~/components/app/encounter/quick-shortcut.vue'
///// Declarations and Flows
// Props
const props = defineProps<{
classCode: EncounterProps['classCode']
subClassCode?: EncounterProps['subClassCode']
}>()
// Common preparations
const route = useRoute()
const router = useRouter()
// activeTab selalu sinkron dengan query param
const activeTab = computed({
get: () => (route.query?.tab && typeof route.query.tab === 'string' ? route.query.tab : 'status'),
set: (val: string) => {
router.replace({ path: route.path, query: { tab: val } })
const { user, userActiveRole, getActiveRole } = useUserStore()
const activeRole = getActiveRole()
const activePosition = ref(getServicePosition(activeRole))
const menus = ref([] as any)
const activeMenu = computed({
get: () => (route.query?.menu && typeof route.query.menu === 'string' ? route.query.menu : 'status'),
set: (value: string) => {
router.replace({ path: route.path, query: { menu: value } })
},
})
const id = typeof route.params.id == 'string' ? parseInt(route.params.id) : 0
const data = ref(genEncounter())
const data = ref<Encounter>(genEncounter())
const isShowPatient = computed(() => data.value && data.value?.patient?.person)
async function fetchDetail() {
const res = await getDetail(id, {
includes:
'patient,patient-person,patient-person-addresses,unit,Appointment_Doctor,Appointment_Doctor-employee,Appointment_Doctor-employee-person,EncounterDocuments',
})
if (res.body?.data) data.value = res.body?.data
const { setOpen } = useSidebar()
setOpen(false)
if (activePosition.value === 'none') { // if user position is none, redirect to home page
router.push('/')
}
onMounted(() => {
fetchDetail()
})
const tabs: TabItem[] = [
{ value: 'status', label: 'Status Masuk/Keluar', component: Status, props: { encounter: data } },
// Dummy rows for ProtocolList (matches keys expected by list-cfg.protocol)
const protocolRows = [
{
value: 'early-medical-assessment',
label: 'Pengkajian Awal Medis',
component: EarlyMedicalAssesmentList,
props: { encounter: data, type: 'early-medic', label: 'Pengkajian Awal Medis' },
number: '1',
tanggal: new Date().toISOString().substring(0, 10),
siklus: 'I',
periode: 'Siklus I',
kehadiran: 'Hadir',
action: '',
},
{
value: 'rehab-medical-assessment',
label: 'Pengkajian Awal Medis Rehabilitasi Medis',
component: EarlyMedicalRehabList,
props: { encounter: data, type: 'early-rehab', label: 'Pengkajian Awal Medis Rehabilitasi Medis' },
},
{
value: 'function-assessment',
label: 'Asesmen Fungsi',
component: AssesmentFunctionList,
props: { encounter: data, type: 'function', label: 'Asesmen Fungsi' },
},
{ value: 'therapy-protocol', label: 'Protokol Terapi' },
{ value: 'education-assessment', label: 'Asesmen Kebutuhan Edukasi' },
{ value: 'patient-note', label: 'CPRJ', component: Cprj, props: { encounter: data } },
{ value: 'consent', label: 'General Consent', component: GeneralConsentList, props: { encounter: data } },
{ value: 'prescription', label: 'Order Obat', component: Prescription, props: { encounter_id: data.value.id } },
{ value: 'device-order', label: 'Order Alkes', component: DeviceOrder, props: { encounter_id: data.value.id } },
{ value: 'device', label: 'Order Alkes' },
{ value: 'mcu-radiology', label: 'Order Radiologi', component: Radiology, props: { encounter_id: data.id } },
{ value: 'mcu-lab-cp', label: 'Order Lab PK', component: CpLabOrder, props: { encounter_id: data.id } },
{ value: 'mcu-lab-micro', label: 'Order Lab Mikro' },
{ value: 'mcu-lab-pa', label: 'Order Lab PA' },
{ value: 'medical-action', label: 'Order Ruang Tindakan' },
{ value: 'mcu-result', label: 'Hasil Penunjang' },
{ value: 'consultation', label: 'Konsultasi', component: Consultation, props: { encounter: data } },
{ value: 'resume', label: 'Resume', component: ResumeList, props: { encounter: data } },
{ 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 },
number: '2',
tanggal: new Date().toISOString().substring(0, 10),
siklus: 'II',
periode: 'Siklus II',
kehadiran: 'Tidak Hadir',
action: '',
},
]
const paginationMeta = {
recordCount: protocolRows.length,
page: 1,
pageSize: 10,
totalPage: 1,
hasNext: false,
hasPrev: false,
}
// Reacrtivities
watch(getActiveRole, () => {
activePosition.value = getServicePosition(userActiveRole)
initMenus()
})
onMounted(async () => {
await getData()
initMenus()
})
///// Functions
function handleClick(type: string) {
if (type === 'draft') {
router.back()
}
}
function initMenus() {
menus.value = getMenuItems(id, props, user, {
encounter: data.value
} as any, {
protocolTheraphy: paginationMeta,
protocolChemotherapy: paginationMeta,
medicineProtocolChemotherapy: paginationMeta,
})
}
async function getData() {
data.value = await getEncounterData(id)
}
</script>
<template>
<div class="w-full">
<div class="mb-4">
<PubMyUiNavContentBa label="Kembali ke Daftar Kunjungan" />
<div class="bg-white dark:bg-slate-800 p-4 2xl:p-5">
<div class="mb-4 flex">
<div>
<ContentNavBa label="Kembali" @click="handleClick" />
</div>
<!-- <div class="ms-auto pe-3 pt-1 text-end text-xl 2xl:text-2xl font-semibold">
Pasien: {{ data.patient.person.name }} --- No. RM: {{ data.patient.number }}
</div> -->
</div>
<ContentSwitcher :active="1" :height="150">
<template v-slot:content1>
<EncounterPatientInfo v-if="isShowPatient" :data="data" />
</template>
<template v-slot:content2>
<EncounterHistoryButtonMenu v-if="isShowPatient" />
</template>
</ContentSwitcher>
</div>
<AppEncounterQuickInfo :data="data" />
<CompTab
:data="tabs"
:initial-active-tab="activeTab"
@change-tab="activeTab = $event"
/>
<SubMenu :data="menus" :initial-active-menu="activeMenu" @change-menu="activeMenu = $event" />
</div>
</template>
+10 -1
View File
@@ -48,9 +48,18 @@ async function setMenu() {
const activeRoleParts = activeRole ? activeRole.split('|') : []
const role = activeRoleParts[0]+(activeRoleParts.length > 1 ? `-${activeRoleParts[1]}` : '')
try {
const res = await fetch(`/side-menu-items/${role.toLowerCase()}.json`)
const rawMenu = await res.text()
navMenu.value = JSON.parse(rawMenu)
const parsedMenu = JSON.parse(rawMenu)
const { user } = useUserStore()
if(user.unit_code == 'rehab') {
parsedMenu[0].heading = 'Rehab Medik'
parsedMenu[0].items = parsedMenu[0].items.filter((item: any) => item.title != 'IGD')
}
navMenu.value = parsedMenu
} catch (e) {
const res = await fetch(`/side-menu-items/blank.json`)
const rawMenu = await res.text()
@@ -1,6 +1,6 @@
<script setup lang="ts">
const props = defineProps<{
height: 200
height?: number
activeTab?: 1 | 2
}>()
@@ -12,7 +12,7 @@ function handleClick(value: 1 | 2) {
</script>
<template>
<div class="content-switcher" :style="`height: ${height}px`">
<div class="content-switcher" :style="`height: ${height || 200}px`">
<div :class="`${activeTab === 1 ? 'active' : 'inactive'}`">
<div class="content-wrapper">
<div>
@@ -49,7 +49,7 @@ function handleClick(value: 1 | 2) {
}
.content-wrapper {
@apply p-4 2xl:p-5 overflow-hidden
@apply p-4 2xl:p-5 overflow-hidden grow
}
.inactive .content-wrapper {
@apply p-0 w-0
+4 -2
View File
@@ -1,7 +1,9 @@
<script lang="ts" setup>
const { class: classProp } = defineProps<{
class?: string
}>()
</script>
<template>
<div class="w-5 text-center">:</div>
<div :class="`w-4 ps-1 ${classProp}`">:</div>
</template>
+16 -10
View File
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { type EncounterItem } from "../../../../handlers/encounter-process.handler";
import { type EncounterItem } from "~/handlers/encounter-init.handler";
const props = defineProps<{
initialActiveMenu: string
@@ -18,22 +18,28 @@ function changeMenu(value: string) {
</script>
<template>
<div class="mt-4 flex gap-4">
<div class="flex">
<!-- Menu Sidebar -->
<div v-if="data.length > 0" class="w-72 flex-shrink-0 rounded-md border bg-white shadow-sm dark:bg-neutral-950">
<div class="max-h-[calc(100vh-12rem)] overflow-y-auto p-2">
<button v-for="menu in data" :key="menu.id" :data-active="activeMenu === menu.id"
class="w-full rounded-md px-4 py-3 text-left text-sm transition-colors data-[active=false]:text-gray-700 data-[active=false]:hover:bg-gray-100 data-[active=true]:bg-primary data-[active=true]:text-white dark:data-[active=false]:text-gray-300 dark:data-[active=false]:hover:bg-neutral-800"
<div v-if="data.length > 0" class="w-56 2xl:w-64 flex-shrink-0 rounded-md border bg-white dark:bg-slate-800 shadow-sm">
<div class="max-h-[calc(100vh-12rem)] overflow-y-auto px-2 py-3">
<button
v-for="menu in data"
:key="menu.id"
:data-active="activeMenu === menu.id"
class="w-full rounded-md px-4 py-3 text-left text-xs 2xl:text-sm transition-colors data-[active=false]:text-gray-700 data-[active=false]:hover:bg-gray-100 data-[active=true]:bg-primary data-[active=true]:text-white dark:data-[active=false]:text-gray-300 dark:data-[active=false]:hover:bg-neutral-800"
@click="changeMenu(menu.id)">
{{ menu.title }}
</button>
</div>
</div>
<!-- Active Menu Content -->
<div v-if="data.find((m) => m.id === activeMenu)?.component"
class="flex-1 rounded-md border bg-white p-4 shadow-sm dark:bg-neutral-950">
<component :is="data.find((m) => m.id === activeMenu)?.component"
v-bind="data.find((m) => m.id === activeMenu)?.props" />
<div class="p-4 2xl:p-5 flex-grow">
<div v-if="data.find((m) => m.id === activeMenu)?.component"
class="flex-1 rounded-md border bg-white p-4 shadow-sm dark:bg-neutral-950">
<component
:is="data.find((m) => m.id === activeMenu)?.component"
v-bind="data.find((m) => m.id === activeMenu)?.props" />
</div>
</div>
</div>
</template>
+1 -1
View File
@@ -20,7 +20,7 @@ function onClick(type: ClickType) {
@click="onClick('draft')"
class="flex items-center gap-2 rounded-full border border-orange-400 bg-orange-50 px-3 py-1 text-sm font-medium text-orange-600 hover:bg-orange-100"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
</svg>
{{ props.label }}
@@ -37,6 +37,10 @@ function btnClick() {
</div>
</div>
<div class="flex items-center">
<!-- For components as slots -->
<slot />
<!-- For components passed by props -->
<div v-if="prep.components">
<template v-for="cwp in prep.components">
<component
@@ -32,7 +32,7 @@ function btnClick() {
/>
</div>
<div v-if="prep.addNav" class="m-2 flex items-center">
<Button size="md" class="rounded-md border border-gray-300 px-4 py-2 text-white sm:text-sm" @click="btnClick">
<Button size="default" class="rounded-md border border-gray-300 px-4 py-2 text-white sm:text-sm" @click="btnClick">
<Icon name="i-lucide-plus" class="mr-2 h-4 w-4 align-middle" />
{{ prep.addNav.label }}
</Button>
+24 -16
View File
@@ -1,4 +1,5 @@
import type { Permission, RoleAccess } from '~/models/role'
import type { Permission, RoleAccesses } from '~/models/role'
import { systemCode } from '~/const/common/role'
export interface PageOperationPermission {
canRead: boolean
@@ -7,7 +8,6 @@ export interface PageOperationPermission {
canDelete: boolean
}
/**
* Check if user has access to a page
*/
@@ -15,19 +15,27 @@ export function useRBAC() {
// NOTE: this roles was dummy for testing only, it should taken from the user store
const authStore = useUserStore()
const checkRole = (roleAccess: RoleAccess, _userRoles?: string[]): boolean => {
const roles = authStore.userRole
return roles.some((role: string) => role === 'system' || (role in roleAccess)) // system by-passes this check
const checkRole = (roleAccesses: RoleAccesses, _userRoles?: string[]): boolean => {
const activeRole = authStore.getActiveRole() || ''
if (activeRole === systemCode) {
return true
}
return (activeRole in roleAccesses);
}
const checkPermission = (roleAccess: RoleAccess, permission: Permission, _userRoles?: string[]): boolean => {
const roles = authStore.userRole
return roles.some((role: string) => role === 'system' || roleAccess[role]?.includes(permission)) // system by-passes this check
const checkPermission = (roleAccesses: RoleAccesses, permission: Permission, _userRoles?: string[]): boolean => {
const activeRole = authStore.getActiveRole() || ''
if (activeRole === systemCode) {
return true
}
if (activeRole in roleAccesses && roleAccesses[activeRole]) {
return roleAccesses[activeRole].includes(permission)
}
return false
}
const getUserPermissions = (roleAccess: RoleAccess, _userRoles?: string[]): Permission[] => {
const roles = authStore.userRole
// const roles = ['admisi']
const getUserPermissions = (roleAccess: RoleAccesses, _userRoles?: string[]): Permission[] => {
const roles = authStore.userRoles
const permissions = new Set<Permission>()
roles.forEach((role: string) => {
@@ -39,12 +47,12 @@ export function useRBAC() {
return Array.from(permissions)
}
const hasCreateAccess = (roleAccess: RoleAccess) => checkPermission(roleAccess, 'C')
const hasReadAccess = (roleAccess: RoleAccess) => checkPermission(roleAccess, 'R')
const hasUpdateAccess = (roleAccess: RoleAccess) => checkPermission(roleAccess, 'U')
const hasDeleteAccess = (roleAccess: RoleAccess) => checkPermission(roleAccess, 'D')
const hasCreateAccess = (roleAccess: RoleAccesses) => checkPermission(roleAccess, 'C')
const hasReadAccess = (roleAccess: RoleAccesses) => checkPermission(roleAccess, 'R')
const hasUpdateAccess = (roleAccess: RoleAccesses) => checkPermission(roleAccess, 'U')
const hasDeleteAccess = (roleAccess: RoleAccesses) => checkPermission(roleAccess, 'D')
const getPagePermissions = (roleAccess: RoleAccess): PageOperationPermission => ({
const getPagePermissions = (roleAccess: RoleAccesses): PageOperationPermission => ({
canRead : hasReadAccess(roleAccess),
canCreate: hasCreateAccess(roleAccess),
canUpdate: hasUpdateAccess(roleAccess),
+36
View File
@@ -0,0 +1,36 @@
export const systemCode = 'system'
export const rehabInstCode = 'rehab'
export const rehabUnitCode = 'rehab'
export const headPosCode = 'head' // head position
export const respPosCode = 'resp' // responsible position, verificator
export type UnitLevel =
'inst' | // installation
'unit' | // unit / poly
'spec' | // specialist
'subspec' // subspecialist
export const medicalRoles = [
'emp|doc', // doctor
'emp|nur', // nurse
'emp|miw', // midwife
'emp|thr', // therapist
'emp|nut', // nutritionist
'emp|pha', // pharmacy
'emp|lab' // laborant
]
export const serviceRoles = [
'emp|reg',
...medicalRoles,
]
export function genSpecHeadCode(unit_level: UnitLevel, unit_code: string): string {
return `${unit_level}|${unit_code}|${headPosCode}`
}
export function genUnitRespCode(unit_level: UnitLevel, unit_code: string): string {
return `${unit_level}|${unit_code}|${respPosCode}`
}
@@ -4,13 +4,13 @@ import type { Permission } from "~/models/role";
// export type Keys = 'key1' | 'key2' | 'key3' | etc
export const permissions: Record<string, Record<string, Permission[]>> = {
'/outpatient/registration-queue': {
'/ambulatory/registration-queue': {
'emp|reg': ['R', 'U', 'D'],
},
'/outpatient/encounter-queue': {
'/ambulatory/encounter-queue': {
'emp|nur': ['R', 'U', 'D'],
},
'/outpatient/encounter': {
'/ambulatory/encounter': {
'emp|reg': ['C', 'R', 'U', 'D'],
'emp|doc': ['R'],
'emp|nur': ['R'],
@@ -21,10 +21,10 @@ export const permissions: Record<string, Record<string, Permission[]>> = {
'emp|lab': ['R'],
'emp|rad': ['R'],
},
'/outpatient/encounter/add': {
'/ambulatory/encounter/add': {
'emp|reg': ['C', 'R', 'U', 'D'],
},
'/outpatient/encounter/[id]/detail': {
'/ambulatory/encounter/[id]': {
'emp|reg': ['C', 'R', 'U', 'D'],
'emp|doc': ['R'],
'emp|nur': ['R'],
@@ -35,10 +35,10 @@ export const permissions: Record<string, Record<string, Permission[]>> = {
'emp|lab': ['R'],
'emp|rad': ['R'],
},
'/outpatient/encounter/[id]/edit': {
'/ambulatory/encounter/[id]/edit': {
'emp|reg': ['C', 'R', 'U', 'D'],
},
'/outpatient/encounter/[id]/process': {
'/ambulatory/encounter/[id]/process': {
'emp|doc': ['R', 'U'],
'emp|nur': ['R', 'U'],
'emp|thr': ['R', 'U'],
@@ -48,10 +48,10 @@ export const permissions: Record<string, Record<string, Permission[]>> = {
'emp|lab': ['R', 'U'],
'emp|rad': ['R', 'U'],
},
'/outpatient/consulation': {
'/ambulatory/consulation': {
'emp|doc': ['R'],
},
'/outpatient/consulation/[id]/process': {
'/ambulatory/consulation/[id]/process': {
'emp|doc': ['R', 'U'],
},
}
+16
View File
@@ -0,0 +1,16 @@
import type { Permission } from "~/models/role";
// Should we define the keys first?
// export type Keys = 'key1' | 'key2' | 'key3' | etc
export const permissions: Record<string, Record<string, Permission[]>> = {
'/chemotherapy/adm': {
'emp|reg': ['R'],
'emp|nur': ['R', 'U', 'D'],
},
'/outpatient/series': {
'emp|nur': ['R', 'U', 'D'],
'emp|doc': ['R', 'U', 'D'],
'emp|thr': ['R', 'U', 'D'],
},
}
+6 -5
View File
@@ -1,5 +1,5 @@
import { isValidDate } from '~/lib/date'
import { medicalPositions } from '~/lib/roles'
import { medicalRoles } from '~/const/common/role'
export interface EncounterItem {
id: string
@@ -444,7 +444,8 @@ export function getMenuItems(
data: EncounterListData,
meta: any,
) {
const normalClassCode = props.classCode === 'ambulatory' ? 'outpatient' : props.classCode
// const normalClassCode = props.classCode === 'ambulatory' ? 'outpatient' : props.classCode
const normalClassCode = props.classCode === 'ambulatory' ? 'ambulatory' : props.classCode
const currentKeys = injectComponents(id, data, meta)
const defaultItems: EncounterItem[] = Object.values(currentKeys)
const listItemsForOutpatientRehab = mergeArrayAt(
@@ -456,14 +457,14 @@ export function getMenuItems(
getItemsAll('ambulatory', 'chemo', defaultItems),
)
const listItems: Record<string, Record<string, Record<string, any>>> = {
'installation|outpatient': {
'installation|ambulatory': {
'unit|rehab': {
items: listItemsForOutpatientRehab,
roles: medicalPositions,
roles: medicalRoles,
},
'unit|chemo': {
items: listItemsForOutpatientChemo,
roles: medicalPositions,
roles: medicalRoles,
},
all: getItemsAll('ambulatory', 'all', defaultItems),
},
+8 -2
View File
@@ -2,7 +2,12 @@
import CardContent from '~/components/pub/ui/card/CardContent.vue'
const route = useRoute()
const contentFrame = computed(() => route.meta.contentFrame)
const contentPadding = computed(() => route.meta.contentPadding || 'p-4 2xl:p-5')
const contentUseCard = computed(() => route.meta.contentUseCard === false ? false : true)
console.log(route.meta.contentUseCard,contentUseCard)
const contentFrameClass = computed(() => {
switch (contentFrame.value) {
case 'cf-container-2xl':
@@ -28,13 +33,14 @@ const contentFrameClass = computed(() => {
<LayoutAppSidebar />
<SidebarInset>
<LayoutHeader />
<div :class="`w-full p-4 2xl:p-5 flex justify-center ${contentFrameClass}`">
<div :class="`w-full flex justify-center ${contentPadding} ${contentFrameClass}`">
<div v-if="contentFrame !== 'cf-no-frame'">
<Card>
<Card v-if="contentUseCard">
<CardContent>
<slot />
</CardContent>
</Card>
<slot v-else />
</div>
<slot v-else />
</div>
+13 -11
View File
@@ -1,14 +1,16 @@
export const medicalPositions = ['emp|doc', 'emp|lab', 'emp|mid', 'emp|nur', 'emp|nut', 'emp|pha', 'emp|reg']
const verificatorRole = 'verificator'
import { medicalRoles, respPosCode } from '~/const/common/role'
export function getPositionAs(roleAccess: string): string {
if (roleAccess.includes('|')) {
if (medicalPositions.includes(roleAccess)) {
return 'medical'
}
if (roleAccess.includes(verificatorRole)) {
return 'verificator'
}
export function getServicePosition(role?: string): string {
if(!role) {
return 'none'
}
if (medicalRoles.includes(role)) {
return 'medical'
} else if (role === 'emp|reg') {
return 'registration'
} else if (role.includes('|resp')) {
return 'verificator'
} else {
return 'none'
}
return 'none'
}
+1 -1
View File
@@ -20,7 +20,7 @@ export default defineNuxtRouteMiddleware((to) => {
const requiredRoles = to.meta.roles as string[]
if (requiredRoles && requiredRoles.length > 0) {
// FIXME: change this dummy roles, when api is ready
const userRoles = authStore.userRole
const userRoles = authStore.userRoles
// const userRoles = ['admisi']
const hasRequiredRole = requiredRoles.some((role) => userRoles.includes(role))
+3 -3
View File
@@ -14,9 +14,9 @@ export interface AuthState {
export type Permission = 'C' | 'R' | 'U' | 'D'
export interface RoleAccess {
export interface RoleAccesses {
[role: string]: Permission[]
}
export type PagePath = keyof typeof PAGE_PERMISSIONS
export type PagePermission = (typeof PAGE_PERMISSIONS)[PagePath]
// export type PagePath = keyof typeof PAGE_PERMISSIONS
// export type PagePermission = (typeof PAGE_PERMISSIONS)[PagePath]
@@ -1,50 +1,57 @@
<script setup lang="ts">
import type { PagePermission } from '~/models/role'
// Pubs
import Error from '~/components/pub/my-ui/error/error.vue'
import { PAGE_PERMISSIONS } from '~/lib/page-permission'
// Models & Consts
import type { Permission } from '~/models/role'
import { permissions } from '~/const/page-permission/ambulatory'
// Apps
import Content from '~/components/content/encounter/entry.vue'
// Page meta
definePageMeta({
middleware: ['rbac'],
roles: ['doctor', 'nurse', 'admisi', 'pharmacy', 'billing', 'management'],
roles: ['emp|reg'],
title: 'Edit Kunjungan',
contentFrame: 'cf-full-width',
})
const route = useRoute()
useHead({
title: () => `${route.meta.title}`, // backtick to avoid the ts-plugin(2322) warning
})
const roleAccess: PagePermission = PAGE_PERMISSIONS['/outpatient/encounter']
// Preps role checking
const roleAccess: Record<string, Permission[]> = permissions['/ambulatory/encounter/[id]/edit'] || {}
const { checkRole, hasUpdateAccess } = useRBAC()
// Check if user has access to this page
// Check if user has access to this page, need to use try - catch for proper handling
const hasAccess = checkRole(roleAccess)
if (!hasAccess) {
throw createError({
statusCode: 403,
statusMessage: 'Access denied',
})
navigateTo('/403')
}
// Define permission-based computed properties
const canUpdate = hasUpdateAccess(roleAccess)
// Get encounter ID from route params
const encounterId = computed(() => {
const encounter_id = computed(() => {
const id = route.params.id
return typeof id === 'string' ? parseInt(id) : 0
})
// User info
const { user } = useUserStore()
const subClassCode = user.unit_code == 'rehab' ? 'rehab' : 'reg'
</script>
<template>
<div v-if="canUpdate">
<ContentEncounterEntry
:id="encounterId"
<Content
:id="encounter_id"
class-code="ambulatory"
sub-class-code="reg"
:sub-class-code="subClassCode"
form-type="Edit"
/>
</div>
@@ -0,0 +1,47 @@
<script setup lang="ts">
// Pubs
import Error from '~/components/pub/my-ui/error/error.vue'
// Models & Consts
import type { Permission } from '~/models/role'
import { permissions } from '~/const/page-permission/ambulatory'
import { medicalRoles } from '~/const/common/role'
// Page meta
definePageMeta({
middleware: ['rbac'],
roles: medicalRoles,
title: 'Detail Kunjungan',
contentFrame: 'cf-container-md',
})
// Define common things
const route = useRoute()
// Prep role checking
const roleAccess: Record<string, Permission[]> = permissions['/ambulatory/encounter/[id]'] || {}
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)
// Page needs
useHead({
title: () => route.meta.title as string,
})
</script>
<template>
<div>
<div v-if="canRead">
<ContentOutpatientEncounterDetail :patient-id="Number(route.params.id)" />
</div>
<Error v-else :status-code="403" />
</div>
</template>
@@ -1,18 +1,23 @@
<script setup lang="ts">
import type { Permission } from '~/models/role'
import { permissions } from '~/const/page-permission/outpatient'
// Pubs
import Error from '~/components/pub/my-ui/error/error.vue'
import Content from '~/components/content/encounter/process-next.vue'
// AppS
import type { Permission } from '~/models/role'
import { permissions } from '~/const/page-permission/ambulatory'
import Content from '~/components/content/encounter/process.vue'
definePageMeta({
middleware: ['rbac'],
roles: ['emp|doc', 'emp|nur', 'emp|reg', 'emp|pha', 'emp|pay', 'emp|mng'],
title: 'Tambah Kunjungan',
roles: ['emp|doc', 'emp|nur', 'emp|miw', 'emp|nut', 'emp|lab', 'emp|pha', 'emp|thr'],
title: 'Proses Kunjungan',
contentFrame: 'cf-full-width',
contentPadding: 'p-0',
contentUseCard: false
})
// Preps role checking
const roleAccess: Record<string, Permission[]> = permissions['/outpatient/encounter'] || {}
const roleAccess: Record<string, Permission[]> = permissions['/ambulatory/encounter/[id]/process'] || {}
const { checkRole, hasReadAccess } = useRBAC()
// Check if user has access to this page
@@ -29,11 +34,12 @@ const route = useRoute()
useHead({
title: () => `${route.meta.title}`,
})
</script>
<template>
<div v-if="canRead">
<Content class-code="ambulatory" sub-class-code="reg" />
<Content class-code="ambulatory" />
</div>
<Error v-else :status-code="403" />
</template>
@@ -1,6 +1,6 @@
<script setup lang="ts">
import type { Permission } from '~/models/role'
import { permissions } from '~/const/page-permission/outpatient'
import { permissions } from '~/const/page-permission/ambulatory'
import Error from '~/components/pub/my-ui/error/error.vue'
import Content from '~/components/content/encounter/entry.vue'
@@ -13,7 +13,7 @@ definePageMeta({
})
// Preps role checking
const roleAccess: Record<string, Permission[]> = permissions['/outpatient/encounter'] || {}
const roleAccess: Record<string, Permission[]> = permissions['/ambulatory/encounter/add'] || {}
const { checkRole, hasReadAccess, hasCreateAccess } = useRBAC()
// Check if user has access to this page
@@ -1,43 +1,45 @@
<script setup lang="ts">
import type { Permission } from '~/models/role'
import { permissions } from '~/const/page-permission/outpatient'
// Pubs
import Error from '~/components/pub/my-ui/error/error.vue'
// Models & Consts
import type { Permission } from '~/models/role'
import { permissions } from '~/const/page-permission/ambulatory'
// Apps
import Content from '~/components/content/encounter/list.vue'
// Page meta
definePageMeta({
middleware: ['rbac'],
roles: ['emp|reg', 'emp|nur', 'emp|doc', 'emp|miw', 'emp|thr', 'emp|nut', 'emp|pha', 'emp|lab'],
// middleware: ['rbac'],
// roles: ['emp|reg', 'emp|nur', 'emp|doc', 'emp|miw', 'emp|thr', 'emp|nut', 'emp|pha', 'emp|lab'],
title: 'Daftar Kunjungan',
contentFrame: 'cf-full-width',
})
// Define common things
const route = useRoute()
// Preps role checking
const roleAccess: Record<string, Permission[]> = permissions['/outpatient/encounter'] || {}
const { checkRole, hasReadAccess } = useRBAC()
const roleAccess: Record<string, Permission[]> = permissions[route.path] || {}
const { checkRole, hasCreateAccess, hasReadAccess } = useRBAC()
// Check if user has access to this page
const hasAccess = checkRole(roleAccess)
if (!hasAccess) {
const canRead = hasReadAccess(roleAccess)
if (!hasAccess || !canRead) {
navigateTo('/403')
}
// Define permission-based computed properties
const canRead = hasReadAccess(roleAccess)
const canCreate = hasCreateAccess(roleAccess)
// Page needs
const route = useRoute()
useHead({
title: () => route.meta.title as string,
})
const { user, getActiveRole } = useUserStore()
// const activeRole = getActiveRole()
// const activeRoleParts = activeRole ? activeRole.split('|') : ['', '']
// const activeRolePos = activeRoleParts[0] // reaching this page means it is already set
// const activeRoleType = activeRoleParts[1] == 'rehab' ? activeRoleParts[1] : ''
// User info
const { user } = useUserStore()
const subClassCode = user.unit_code == 'rehab' ? 'rehab' : 'reg'
</script>
<template>
@@ -46,7 +48,7 @@ const subClassCode = user.unit_code == 'rehab' ? 'rehab' : 'reg'
<Content
class-code="ambulatory"
:sub-class-code="subClassCode"
type="encounter"
:can-create="canCreate"
/>
</div>
<Error
@@ -0,0 +1,47 @@
<script setup lang="ts">
import type { PagePermission } from '~/models/role'
import Error from '~/components/pub/my-ui/error/error.vue'
import { PAGE_PERMISSIONS } from '~/lib/page-permission'
import ContentChemotherapyAdminList from '~/components/content/chemotherapy/admin-list.vue'
import ContentChemotherapyVerification from '~/components/content/chemotherapy/verification.vue'
definePageMeta({
middleware: ['rbac'],
roles: ['doctor', 'nurse', 'admisi', 'pharmacy', 'billing', 'management'],
title: 'Kemoterapi Admin',
contentFrame: 'cf-full-width',
})
const route = useRoute()
useHead({
title: () => 'Verifikasi Jadwal Pasien',
})
const roleAccess: PagePermission = PAGE_PERMISSIONS['/doctor'] || {}
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 = true // hasReadAccess(roleAccess)
const mode = computed(() => route.params.mode as string)
</script>
<template>
<div>
<div v-if="canRead">
<ContentChemotherapyVerification />
</div>
<Error
v-else
:status-code="403"
/>
</div>
</template>
@@ -0,0 +1,55 @@
<script setup lang="ts">
import type { PagePermission } from '~/models/role'
import Error from '~/components/pub/my-ui/error/error.vue'
import { PAGE_PERMISSIONS } from '~/lib/page-permission'
import ContentChemotherapyAdminList from '~/components/content/chemotherapy/admin-list.vue'
import ContentChemotherapyVerification from '~/components/content/chemotherapy/verification.vue'
definePageMeta({
middleware: ['rbac'],
roles: ['doctor', 'nurse', 'admisi', 'pharmacy', 'billing', 'management'],
title: 'Kemoterapi Admin',
contentFrame: 'cf-full-width',
})
const route = useRoute()
useHead({
title: () => {
const mode = route.params.mode as string
if (mode === 'admin') {
return 'Administrasi Pasien Rawat Jalan Kemoterapi'
} else if (mode === 'series') {
return 'Proses Jadwal Pasien'
}
return route.meta.title as string
},
})
const roleAccess: PagePermission = PAGE_PERMISSIONS['/doctor'] || {}
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 = true // hasReadAccess(roleAccess)
const mode = computed(() => route.params.mode as string)
</script>
<template>
<div>
<div v-if="canRead">
<ContentChemotherapyAdminList v-if="mode === 'admin'" />
<ContentChemotherapyProcess v-else-if="mode === 'series'" />
<Error v-else :status-code="404" status-message="Mode tidak ditemukan" />
</div>
<Error v-else :status-code="403" />
</div>
</template>
@@ -0,0 +1,9 @@
<script setup lang="ts">
// Redirect to list page - the process page is now at [id]/index.vue
const route = useRoute()
navigateTo('/outpation-action/chemotherapy/list')
</script>
<template>
<div />
</template>
@@ -1,13 +1,13 @@
<script setup lang="ts">
import type { PagePermission } from '~/models/role'
import type { Permission } from '~/models/role'
import { permissions } from '~/const/page-permission/chemoteraphy'
import Error from '~/components/pub/my-ui/error/error.vue'
import { PAGE_PERMISSIONS } from '~/lib/page-permission'
definePageMeta({
middleware: ['rbac'],
roles: ['doctor', 'nurse', 'admisi', 'pharmacy', 'billing', 'management'],
title: 'Detail Surat Kontrol',
contentFrame: 'cf-container-md',
title: 'Daftar Kempterapi',
contentFrame: 'cf-full-width',
})
const route = useRoute()
@@ -16,25 +16,24 @@ useHead({
title: () => route.meta.title as string,
})
const roleAccess: PagePermission = PAGE_PERMISSIONS['/patient']
const roleAccess: Record<string, Permission[]> = permissions['/chemotherapy'] || {}
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 = hasReadAccess(roleAccess)
const canRead = true
const canRead = hasReadAccess(roleAccess)
</script>
<template>
<div>
<div v-if="canRead">
<ContentOutpatientEncounterDetail :patient-id="Number(route.params.id)" />
<ContentChemotherapyList />
</div>
<Error v-else :status-code="403" />
</div>
@@ -0,0 +1,10 @@
<script setup lang="ts">
const route = useRoute();
</script>
<template>
<div class="p-10 text-center">
<div class="mb-5 text-base font-semibold">Hello world!!</div>
<div>You are accessing "{{ route.fullPath }}"</div>
</div>
</template>
@@ -1,6 +1,6 @@
<script setup lang="ts">
import type { Permission } from '~/models/role'
import { permissions } from '~/const/page-permission/outpatient'
import { permissions } from '~/const/page-permission/ambulatory'
import Error from '~/components/pub/my-ui/error/error.vue'
import Content from '~/components/content/encounter/entry.vue'
@@ -1,6 +1,6 @@
<script setup lang="ts">
import type { Permission } from '~/models/role'
import { permissions } from '~/const/page-permission/outpatient'
import { permissions } from '~/const/page-permission/emergency'
import Error from '~/components/pub/my-ui/error/error.vue'
import Content from '~/components/content/encounter/list.vue'
@@ -13,7 +13,8 @@ definePageMeta({
})
// Preps role checking
const roleAccess: Record<string, Permission[]> = permissions['/outpatient/encounter'] || {}
const route = useRoute()
const roleAccess: Record<string, Permission[]> = permissions[route.path] || {}
const { checkRole, hasReadAccess } = useRBAC()
// Check if user has access to this page
@@ -26,18 +27,12 @@ if (!hasAccess) {
const canRead = hasReadAccess(roleAccess)
// Page needs
const route = useRoute()
useHead({
title: () => route.meta.title as string,
})
const { user, getActiveRole } = useUserStore()
// const activeRole = getActiveRole()
// const activeRoleParts = activeRole ? activeRole.split('|') : ['', '']
// const activeRolePos = activeRoleParts[0] // reaching this page means it is already set
// const activeRoleType = activeRoleParts[1] == 'rehab' ? activeRoleParts[1] : ''
const { user } = useUserStore()
const subClassCode = user.unit_code == 'rehab' ? 'rehab' : 'reg'
</script>
<template>
@@ -45,7 +40,7 @@ const subClassCode = user.unit_code == 'rehab' ? 'rehab' : 'reg'
<div v-if="canRead">
<Content
class-code="emergency"
sub-class-code="reg"
:sub-class-code="subClassCode"
type="encounter"
/>
</div>
+10
View File
@@ -0,0 +1,10 @@
<script setup lang="ts">
const route = useRoute();
</script>
<template>
<div class="p-10 text-center">
<div class="mb-5 text-base font-semibold">Hello world!!</div>
<div>You are accessing "{{ route.fullPath }}"</div>
</div>
</template>
@@ -1,6 +1,6 @@
<script setup lang="ts">
import type { Permission } from '~/models/role'
import { permissions } from '~/const/page-permission/outpatient'
import { permissions } from '~/const/page-permission/ambulatory'
import Error from '~/components/pub/my-ui/error/error.vue'
import Content from '~/components/content/encounter/entry.vue'
@@ -1,6 +1,6 @@
<script setup lang="ts">
import type { Permission } from '~/models/role'
import { permissions } from '~/const/page-permission/outpatient'
import { permissions } from '~/const/page-permission/inpatient'
import Error from '~/components/pub/my-ui/error/error.vue'
import Content from '~/components/content/encounter/list.vue'
@@ -13,7 +13,7 @@ definePageMeta({
})
// Preps role checking
const roleAccess: Record<string, Permission[]> = permissions['/outpatient/encounter'] || {}
const roleAccess: Record<string, Permission[]> = permissions['/inpatient/encounter'] || {}
const { checkRole, hasReadAccess } = useRBAC()
// Check if user has access to this page
@@ -31,11 +31,7 @@ useHead({
title: () => route.meta.title as string,
})
const { user, getActiveRole } = useUserStore()
// const activeRole = getActiveRole()
// const activeRoleParts = activeRole ? activeRole.split('|') : ['', '']
// const activeRolePos = activeRoleParts[0] // reaching this page means it is already set
// const activeRoleType = activeRoleParts[1] == 'rehab' ? activeRoleParts[1] : ''
const { user } = useUserStore()
const subClassCode = user.unit_code == 'rehab' ? 'rehab' : 'reg'
</script>
@@ -1,5 +0,0 @@
<script setup lang="ts"></script>
<template>
<FlowRehabRegistrationHome />
</template>
@@ -1,40 +0,0 @@
<script setup lang="ts">
import type { PagePermission } from '~/models/role'
import { PAGE_PERMISSIONS } from '~/lib/page-permission'
definePageMeta({
middleware: ['rbac'],
roles: ['doctor', 'nurse', 'admisi', 'pharmacy', 'billing', 'management'],
title: 'Tambah SEP Prosedur',
contentFrame: 'cf-full-width',
})
const route = useRoute()
useHead({
title: () => route.meta.title as string,
})
const roleAccess: PagePermission = PAGE_PERMISSIONS['/doctor']
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">
<ContentDoctorAdd />
</div>
<PubMyUiError v-else :status-code="403" />
</template>
+10
View File
@@ -0,0 +1,10 @@
<script setup lang="ts">
const route = useRoute();
</script>
<template>
<div class="p-10 text-center">
<div class="mb-5 text-base font-semibold">Hello world!!</div>
<div>You are accessing "{{ route.fullPath }}"</div>
</div>
</template>
+10
View File
@@ -0,0 +1,10 @@
<script setup lang="ts">
const route = useRoute();
</script>
<template>
<div class="p-10 text-center">
<div class="mb-5 text-base font-semibold">Hello world!!</div>
<div>You are accessing "{{ route.fullPath }}"</div>
</div>
</template>
@@ -5,7 +5,7 @@ import { PAGE_PERMISSIONS } from '~/lib/page-permission'
definePageMeta({
middleware: ['rbac'],
roles: ['doctor', 'nurse', 'admisi', 'pharmacy', 'billing', 'management'],
roles: [],
title: 'Daftar Dokter',
contentFrame: 'cf-full-width',
})
@@ -16,7 +16,7 @@ useHead({
title: () => route.meta.title as string,
})
const roleAccess: PagePermission = PAGE_PERMISSIONS['/doctor']
const roleAccess: PagePermission = PAGE_PERMISSIONS['/tools-equipment-src/medicine']!
const { checkRole, hasReadAccess } = useRBAC()
+12 -7
View File
@@ -1,19 +1,21 @@
export const useUserStore = defineStore(
'user',
() => {
// should be auth type
const user = ref<any | null>(null)
// const token = useCookie('authentication')
const isAuthenticated = computed(() => !!user.value)
const userRole = computed(() => {
const userRoles = computed(() => {
return user.value?.roles || []
// return roles.map((input: string) => {
// const parts = input.split('|')
// return parts.length > 1 ? parts[1]: parts[0]
// })
})
const userActiveRole = ref('') // watched user failed, so create a ref here
// computed(() => {
// // return user.value?.activeRole || ''
// })
const login = async (userData: any) => {
user.value = userData
}
@@ -25,10 +27,11 @@ export const useUserStore = defineStore(
const setActiveRole = (role: string) => {
if (user.value && user.value.roles.includes(role)) {
user.value.activeRole = role
userActiveRole.value = role
}
}
const getActiveRole = () => {
const getActiveRole = (): string | undefined => {
if (user.value?.activeRole) {
return user.value.activeRole
}
@@ -36,12 +39,14 @@ export const useUserStore = defineStore(
user.value.activeRole = user.value.roles[0]
return user.value.activeRole
}
return undefined
}
return {
user,
isAuthenticated,
userRole,
userRoles,
userActiveRole,
login,
logout,
setActiveRole,
+2 -2
View File
@@ -14,12 +14,12 @@
{
"title": "Kunjungan",
"icon": "i-lucide-stethoscope",
"link": "/outpatient/encounter"
"link": "/ambulatory/encounter"
},
{
"title": "Konsultasi",
"icon": "i-lucide-building-2",
"link": "/outpatient/consultation"
"link": "/ambulatory/consultation"
}
]
},
+1 -1
View File
@@ -10,7 +10,7 @@
{
"title": "Rawat Jalan",
"icon": "i-lucide-stethoscope",
"link": "/outpatient/encounter"
"link": "/ambulatory/encounter"
},
{
"title": "IGD",
+2 -2
View File
@@ -13,11 +13,11 @@
"children": [
{
"title": "Antrian Poliklinik",
"link": "/outpatient/encounter-queue"
"link": "/ambulatory/encounter-queue"
},
{
"title": "Kunjungan",
"link": "/outpatient/encounter"
"link": "/ambulatory/encounter"
}
]
},
+1 -1
View File
@@ -10,7 +10,7 @@
{
"title": "Rawat Jalan",
"icon": "i-lucide-stethoscope",
"link": "/outpatient/encounter"
"link": "/ambulatory/encounter"
},
{
"title": "IGD",
+2 -2
View File
@@ -13,11 +13,11 @@
"children": [
{
"title": "Antrian Pendaftaran",
"link": "/outpatient/registration-queue"
"link": "/ambulatory/registration-queue"
},
{
"title": "Kunjungan",
"link": "/outpatient/encounter"
"link": "/ambulatory/encounter"
}
]
},
+4 -4
View File
@@ -13,19 +13,19 @@
"children": [
{
"title": "Antrian Pendaftaran",
"link": "/outpatient/registration-queue"
"link": "/ambulatory/registration-queue"
},
{
"title": "Antrian Poliklinik",
"link": "/outpatient/encounter-queue"
"link": "/ambulatory/encounter-queue"
},
{
"title": "Kunjungan",
"link": "/outpatient/encounter"
"link": "/ambulatory/encounter"
},
{
"title": "Konsultasi",
"link": "/outpatient/consultation"
"link": "/ambulatory/consultation"
}
]
},
+5 -5
View File
@@ -13,19 +13,19 @@
"children": [
{
"title": "Antrian Pendaftaran",
"link": "/outpatient/registration-queue"
"link": "/ambulatory/registration-queue"
},
{
"title": "Antrian Poliklinik",
"link": "/outpatient/encounter-queue"
"link": "/ambulatory/encounter-queue"
},
{
"title": "Kunjungan",
"link": "/outpatient/encounter"
"link": "/ambulatory/encounter"
},
{
"title": "Konsultasi",
"link": "/outpatient/consultation"
"link": "/ambulatory/consultation"
}
]
},
@@ -389,4 +389,4 @@
}
]
}
]
]