feat (patient): implement patient list and entry form

This commit is contained in:
Abizrh
2025-08-12 17:01:50 +07:00
parent 500cdb6a21
commit 7ae9a9adfe
9 changed files with 342 additions and 13 deletions
+46
View File
@@ -0,0 +1,46 @@
<script setup lang="ts">
import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from '@/components/pub/ui/table'
defineProps<{
rows: unknown[]
cols: object
header: object[]
keys: string[]
funcParsed: object
funcHtml: object
funcComponent: object
}>()
</script>
<template>
<Table>
<TableHeader>
<TableRow>
<TableHead
v-for="(h, idx) in header[0]"
:key="`head-${idx}`"
:style="{ width: cols[idx]?.width ? cols[idx].width + 'px' : undefined }"
>
{{ h.label }}
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow v-for="(row, rowIndex) in rows" :key="`row-${rowIndex}`">
<TableCell v-for="(key, cellIndex) in keys" :key="`cell-${rowIndex}-${cellIndex}`">
<!-- If funcComponent has a renderer -->
<component
v-if="funcComponent[key]"
:is="funcComponent[key](row, rowIndex).component"
v-bind="funcComponent[key](row, rowIndex)"
/>
<!-- If funcParsed or funcHtml returns a value -->
<template v-else>
{{ funcParsed[key]?.(row) ?? funcHtml[key]?.(row) ?? row[key] }}
</template>
</TableCell>
</TableRow>
</TableBody>
</Table>
</template>
@@ -0,0 +1,82 @@
<script setup lang="ts">
import type { ListItemDto, LinkItem } from './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')!
function detail() {
recId.value = props.rec.id || 0
recAction.value = 'showDetail'
recItem.value = props.rec
}
function edit() {
recId.value = props.rec.id || 0
recAction.value = 'showEdit'
recItem.value = props.rec
}
function del() {
recId.value = props.rec.id || 0
recAction.value = 'showConfirmDel'
recItem.value = props.rec
}
const linkItems: LinkItem[] = [
{
label: 'Detail',
onClick: () => {
detail()
},
icon: 'i-lucide-eye',
},
{
label: 'Edit',
onClick: () => {
edit()
},
icon: 'i-lucide-pencil',
},
{
label: 'Hapus',
onClick: () => {
del()
},
icon: 'i-lucide-trash',
},
]
</script>
<template>
<div>
<DropdownMenu>
<DropdownMenuTrigger as-child>
<SidebarMenuButton
size="lg"
class="data-[state=open]:text-sidebar-accent-foreground data-[state=open]:bg-white"
>
<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 bg-white" align="end">
<DropdownMenuGroup>
<DropdownMenuItem
v-for="item in linkItems"
:key="item.label"
v-slot="{ active }"
class="hover:bg-gray-100"
@click="item.onClick"
>
<Icon :name="item.icon" />
<span :class="active ? 'text-sidebar-accent-foreground' : ''">{{ item.label }}</span>
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>
</template>
+20 -8
View File
@@ -1,12 +1,24 @@
<script setup lang="ts"></script>
<script setup lang="ts">
type ClickType = 'cancel' | 'submit'
const emit = defineEmits<{
(e: 'click', type: ClickType): void
}>()
function onClick(type: ClickType) {
emit('click', type)
}
</script>
<template>
<div class="flex justify-between px-2">
<div>
<Button variant="outline">
<Icon name="i-lucide-pencil" class="mr-1" />
Edit
</Button>
</div>
<div class="m-2 flex gap-2 px-2">
<Button class="bg-gray-400" @click="onClick('cancel')">
<Icon name="i-lucide-arrow-left" class="me-2 align-middle" />
Kembali
</Button>
<Button class="bg-primary" @click="onClick('submit')">
<Icon name="i-lucide-check" class="me-2 align-middle" />
Selesai
</Button>
</div>
</template>
+28
View File
@@ -0,0 +1,28 @@
<script setup lang="ts">
type ClickType = 'cancel' | 'draft' | 'submit'
const emit = defineEmits<{
(e: 'click', type: ClickType): void
}>()
function onClick(type: ClickType) {
emit('click', type)
}
</script>
<template>
<div class="m-2 flex gap-2 px-2">
<Button class="bg-gray-400" @click="onClick('cancel')">
<Icon name="i-lucide-arrow-left" class="me-2 align-middle" />
Kembali
</Button>
<Button class="bg-orange-500" variant="outline" @click="onClick('draft')">
<Icon name="i-lucide-file" class="me-2" />
Draf
</Button>
<Button class="bg-primary" @click="onClick('submit')">
<Icon name="i-lucide-check" class="me-2 align-middle" />
Selesai
</Button>
</div>
</template>
+9
View File
@@ -82,3 +82,12 @@ export interface KeyNames {
key: string
label: string
}
export interface LinkItem {
label: string
icon?: string
href?: string // to cover the needs of stating full external origins full url
action?: string // for local paths
onClick?: (event: Event) => void
headerStatus?: boolean
}