feat(encounter): provide cancel of encounter
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
<script setup lang="ts">
|
||||
import type { LinkItem, ListItemDto } from '~/components/pub/my-ui/data/types'
|
||||
import { ActionEvents } from '~/components/pub/my-ui/data/types'
|
||||
|
||||
const props = defineProps<{
|
||||
rec: ListItemDto
|
||||
}>()
|
||||
|
||||
const recId = inject<Ref<number>>('rec_id')!
|
||||
const recAction = inject<Ref<string>>('rec_action')!
|
||||
const recItem = inject<Ref<any>>('rec_item')!
|
||||
const activeKey = ref<string | null>(null)
|
||||
const linkItems: LinkItem[] = [
|
||||
{
|
||||
label: 'Detail',
|
||||
onClick: () => {
|
||||
detail()
|
||||
},
|
||||
icon: 'i-lucide-eye',
|
||||
},
|
||||
{
|
||||
label: 'Edit',
|
||||
onClick: () => {
|
||||
edit()
|
||||
},
|
||||
icon: 'i-lucide-pencil',
|
||||
},
|
||||
{
|
||||
label: 'Print',
|
||||
onClick: () => {
|
||||
print()
|
||||
},
|
||||
icon: 'i-lucide-printer',
|
||||
},
|
||||
{
|
||||
label: 'Batalkan',
|
||||
onClick: () => {
|
||||
cancel()
|
||||
},
|
||||
icon: 'i-lucide-circle-x',
|
||||
},
|
||||
]
|
||||
|
||||
function detail() {
|
||||
recId.value = props.rec.id || 0
|
||||
recAction.value = ActionEvents.showDetail
|
||||
recItem.value = props.rec
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger as-child>
|
||||
<SidebarMenuButton
|
||||
size="lg"
|
||||
class="data-[state=open]:text-sidebar-accent-foreground data-[state=open]:bg-white dark:data-[state=open]:bg-slate-800"
|
||||
>
|
||||
<Icon
|
||||
name="i-lucide-chevrons-up-down"
|
||||
class="ml-auto size-4"
|
||||
/>
|
||||
</SidebarMenuButton>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
class="w-[--radix-dropdown-menu-trigger-width] min-w-40 rounded-lg border border-slate-200 bg-white text-black dark:border-slate-700 dark:bg-slate-800 dark:text-white"
|
||||
align="end"
|
||||
>
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem
|
||||
v-for="item in linkItems"
|
||||
:key="item.label"
|
||||
class="hover:bg-gray-100 dark:hover:bg-slate-700"
|
||||
@click="item.onClick"
|
||||
@mouseenter="activeKey = item.label"
|
||||
@mouseleave="activeKey = null"
|
||||
>
|
||||
<Icon :name="item.icon ?? ''" />
|
||||
<span :class="activeKey === item.label ? 'text-sidebar-accent-foreground' : ''">{{ item.label }}</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</template>
|
||||
@@ -6,7 +6,7 @@ import { getAge } from '~/lib/date'
|
||||
|
||||
type SmallDetailDto = Encounter
|
||||
|
||||
const action = defineAsyncComponent(() => import('~/components/pub/my-ui/data/dropdown-action-pdud.vue'))
|
||||
const action = defineAsyncComponent(() => import('./dropdown-action.vue'))
|
||||
const statusBadge = defineAsyncComponent(() => import('./status-badge.vue'))
|
||||
|
||||
export const config: Config = {
|
||||
|
||||
@@ -14,7 +14,7 @@ import type { HeaderPrep, RefSearchNav } from '~/components/pub/my-ui/data/types
|
||||
import { ActionEvents } from '~/components/pub/my-ui/data/types'
|
||||
|
||||
// Services
|
||||
import { getList as getEncounterList, remove as removeEncounter } from '~/services/encounter.service'
|
||||
import { getList as getEncounterList, remove as removeEncounter, cancel as cancelEncounter } from '~/services/encounter.service'
|
||||
|
||||
// UI
|
||||
import { toast } from '~/components/pub/ui/toast'
|
||||
@@ -35,6 +35,7 @@ const recAction = ref<string>('')
|
||||
const recItem = ref<any>(null)
|
||||
const isFormEntryDialogOpen = ref(false)
|
||||
const isRecordConfirmationOpen = ref(false)
|
||||
const isRecordCancelOpen = ref(false)
|
||||
|
||||
const hreaderPrep: HeaderPrep = {
|
||||
title: 'Kunjungan',
|
||||
@@ -114,6 +115,43 @@ async function getPatientList() {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle confirmation result
|
||||
async function handleConfirmCancel(record: any, action: string) {
|
||||
if (action === 'delete' && record?.id) {
|
||||
try {
|
||||
const result = await cancelEncounter(record.id)
|
||||
if (result.success) {
|
||||
toast({
|
||||
title: 'Berhasil',
|
||||
description: 'Kunjungan berhasil dibatalkan',
|
||||
variant: 'default',
|
||||
})
|
||||
await getPatientList() // Refresh list
|
||||
} else {
|
||||
const errorMessage = result.body?.message || 'Gagal membatalkan kunjungan'
|
||||
toast({
|
||||
title: 'Gagal',
|
||||
description: errorMessage,
|
||||
variant: 'destructive',
|
||||
})
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('Error cancellation encounter:', error)
|
||||
toast({
|
||||
title: 'Gagal',
|
||||
description: error?.message || 'Gagal membatalkan kunjungan',
|
||||
variant: 'destructive',
|
||||
})
|
||||
} finally {
|
||||
// Reset state
|
||||
recId.value = 0
|
||||
recAction.value = ''
|
||||
recItem.value = null
|
||||
isRecordCancelOpen.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle confirmation result
|
||||
async function handleConfirmDelete(record: any, action: string) {
|
||||
if (action === 'delete' && record?.id) {
|
||||
@@ -152,6 +190,14 @@ async function handleConfirmDelete(record: any, action: string) {
|
||||
}
|
||||
|
||||
function handleCancelConfirmation() {
|
||||
// Reset record state when cancelled
|
||||
recId.value = 0
|
||||
recAction.value = ''
|
||||
recItem.value = null
|
||||
isRecordCancelOpen.value = false
|
||||
}
|
||||
|
||||
function handleRemoveConfirmation() {
|
||||
// Reset record state when cancelled
|
||||
recId.value = 0
|
||||
recAction.value = ''
|
||||
@@ -166,16 +212,21 @@ watch(
|
||||
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`)
|
||||
navigateTo(`${basePath}/${recId.value}/process`)
|
||||
} else if (recAction.value === 'showEdit') {
|
||||
navigateTo(`${basePath}/${recId.value}/edit`)
|
||||
} else if (recAction.value === 'showProcess') {
|
||||
navigateTo(`${basePath}/${recId.value}/process`)
|
||||
} else if (recAction.value === 'showPrint') {
|
||||
console.log('print')
|
||||
} else {
|
||||
// handle other actions
|
||||
}
|
||||
@@ -234,12 +285,34 @@ onMounted(() => {
|
||||
</Dialog>
|
||||
|
||||
<!-- Record Confirmation Modal -->
|
||||
<RecordConfirmation
|
||||
v-model:open="isRecordCancelOpen"
|
||||
action="delete"
|
||||
:record="recItem"
|
||||
@confirm="handleConfirmCancel"
|
||||
@cancel="handleCancelConfirmation"
|
||||
>
|
||||
<template #default="{ record }">
|
||||
<div class="text-sm">
|
||||
<p>Apakah anda yakin ingin membatalkan kunjungan pasien berikut?</p>
|
||||
<p v-if="record?.patient?.person?.name">
|
||||
<strong>Nama:</strong>
|
||||
{{ record.patient.person.name }}
|
||||
</p>
|
||||
<p v-if="record?.medical_record_number">
|
||||
<strong>No RM:</strong>
|
||||
{{ record.medical_record_number }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
</RecordConfirmation>
|
||||
|
||||
<RecordConfirmation
|
||||
v-model:open="isRecordConfirmationOpen"
|
||||
action="delete"
|
||||
:record="recItem"
|
||||
@confirm="handleConfirmDelete"
|
||||
@cancel="handleCancelConfirmation"
|
||||
@cancel="handleRemoveConfirmation"
|
||||
>
|
||||
<template #default="{ record }">
|
||||
<div class="text-sm">
|
||||
|
||||
@@ -76,6 +76,8 @@ export const ActionEvents = {
|
||||
showEdit: 'showEdit',
|
||||
showDetail: 'showDetail',
|
||||
showProcess: 'showProcess',
|
||||
showCancel: 'showCancel',
|
||||
showPrint: 'showPrint',
|
||||
}
|
||||
|
||||
export interface DataTableLoader {
|
||||
|
||||
@@ -28,6 +28,11 @@ export function remove(id: number | string) {
|
||||
return base.remove(path, id, name)
|
||||
}
|
||||
|
||||
export function cancel(id: number | string) {
|
||||
let url = `${path}/${id}/cancel`
|
||||
return base.update(url, id, {}, name)
|
||||
}
|
||||
|
||||
export async function getValueLabelList(params: any = null): Promise<{ value: string; label: string }[]> {
|
||||
let data: { value: string; label: string }[] = []
|
||||
const result = await getList(params)
|
||||
|
||||
Reference in New Issue
Block a user