merge dev

This commit is contained in:
2025-11-27 04:50:07 +07:00
parent 4d16ce0f25
commit 3bb10bc556
31 changed files with 161 additions and 617 deletions
-114
View File
@@ -1,114 +0,0 @@
<script setup lang="ts">
import type { FormErrors } from '~/types/error'
import { toTypedSchema } from '@vee-validate/zod'
import FieldGroup from '~/components/pub/my-ui/form/field-group.vue'
import Field from '~/components/pub/my-ui/form/field.vue'
import Label from '~/components/pub/my-ui/form/label.vue'
import Select from '~/components/pub/my-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>
+88 -68
View File
@@ -1,41 +1,46 @@
<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'
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 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
// Pub components
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 type { HeaderPrep, RefSearchNav } from '~/components/pub/my-ui/nav-header/index'
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 * 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'
// 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
}>()
//
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,
@@ -48,33 +53,49 @@ const isFormEntryDialogOpen = 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`)
},
},
}
if (!props.canCreate) {
delete hreaderPrep.addNav
}
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: {},
})
const refSearchNav: RefSearchNav = {
onClick: () => {
// open filter modal
isFormEntryDialogOpen.value = true
console.log(' 1open filter modal')
},
onInput: (_val: string) => {
// filter patient list
@@ -84,27 +105,31 @@ const refSearchNav: RefSearchNav = {
},
}
// Loading state management
// Reactivities
provide('rec_id', recId)
provide('rec_action', recAction)
provide('rec_item', recItem)
provide('table_data_loader', isLoading)
/**
* Get base path for encounter routes based on classCode and subClassCode
*/
function getBasePath(): string {
if (props.classCode === 'ambulatory' && props.subClassCode === 'rehab') {
return '/rehab/encounter'
watch(() => recAction.value, () => {
const basePath = `/${props.classCode}/encounter`
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
}
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
}
recAction.value = '' // reset
})
onMounted(() => {
getPatientList()
})
// Functions
async function getPatientList() {
isLoading.isTableLoading = true
try {
@@ -273,18 +298,11 @@ onMounted(() => {
</script>
<template>
<Header
:prep="{ ...hreaderPrep }"
:ref-search-nav="refSearchNav"
/>
<Separator class="my-4 xl:my-5" />
<CH.ContentHeader v-bind="hreaderPrep">
<FilterNav />
</CH.ContentHeader>
<Filter
:prep="hreaderPrep"
:ref-search-nav="refSearchNav"
/>
<AppEncounterList :data="data" />
<Content :data="data" />
<Dialog
v-model:open="isFormEntryDialogOpen"
@@ -292,13 +310,7 @@ onMounted(() => {
size="lg"
prevent-outside
>
<AppEncounterFilter
:installation="{
msg: { placeholder: 'Pilih' },
items: [],
}"
:schema="{}"
/>
<FilterForm v-bind="filter" />
</Dialog>
<!-- Record Confirmation Modal -->
@@ -326,6 +338,7 @@ onMounted(() => {
</RecordConfirmation>
<RecordConfirmation
v-if="canDelete"
v-model:open="isRecordConfirmationOpen"
action="delete"
:record="recItem"
@@ -349,4 +362,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>
+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()
@@ -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>