Files
simrsx-fe/app/components/pub/base/select-tree/tree-select.vue
T
Khafid Prayoga ba6485a3e7 feat(division): wip tree select component
feat(division): update division list components and add mock api

- Replace patient API endpoint with division mock endpoint
- Simplify table columns and headers for division list
- Add mock API endpoint for division list with tree/flat format

feat(select-tree): add collapsible tree select component with lazy loading

Implement a tree select component with collapsible sections and lazy loading of child items. Includes:
- Collapsible component wrappers for Vue
- Command component wrappers for combobox functionality
- Tree select item component with loading states
- Example implementation in dev page

todo:
- scroll on overflow
- long text truncate possibly with tooltip
- more than > 5 depth of child
- mutate the children lazy
- integration backend for search based text and return  keys

feat(select-tree): add command-item component for tree selection

adjust hover bg-accent (remove state on-highlighted at styling) to avoid conflict on global component

refactor(select-tree): extract TreeItem interface to shared type file

Move TreeItem interface to a dedicated type file for better code organization and reusability. Update components to import the interface and add styling improvements to the tree-select component.

adjust text size for tree to sm

refactor(select-tree): rename tree-select-item to leaf and improve component

- Rename component to better reflect its purpose as a leaf node
- Improve UI with better spacing and hover states
- Simplify toggle logic using v-model
- Add checkmark icon for selected items

checkpoint

wip
2025-09-11 09:50:18 +07:00

69 lines
2.0 KiB
Vue

<script setup lang="ts">
import type { TreeItem } from './type'
import { ChevronsUpDown } from 'lucide-vue-next'
import { cn } from '~/lib/utils'
import TreeView from './tree-view.vue'
const props = defineProps<{
data: TreeItem[]
onFetchChildren: (parentId: string) => Promise<void>
}>()
const modelValue = defineModel<string>()
const open = ref(false)
function handleSelect(newVal: string) {
modelValue.value = newVal
open.value = false
}
function findLabel(value: string, items: TreeItem[]): string | undefined {
for (const item of items) {
if (item.value === value) return item.label
if (item.children) {
const found = findLabel(value, item.children)
if (found) return found
}
}
}
const selectedLabel = computed(() => {
return modelValue.value ? findLabel(modelValue.value, props.data) : '--- select item'
})
</script>
<template>
<Popover v-model:open="open">
<PopoverTrigger as-child>
<Button variant="outline" role="combobox" class="w-full justify-between bg-white border-1 border-gray-400">
<span
class="font-normal text-muted-foreground" :class="cn(
'font-normal',
!modelValue && 'text-muted-foreground',
modelValue && 'text-black',
)"
>
{{ selectedLabel }}
</span>
<ChevronsUpDown class="w-4 h-4 ml-2 opacity-50 shrink-0" />
</Button>
</PopoverTrigger>
<PopoverContent class="min-w-full max-w-[350px] p-0">
<Command>
<CommandInput placeholder="Cari item..." />
<CommandEmpty>Item tidak ditemukan.</CommandEmpty>
<CommandList class="max-h-[300px] overflow-x-auto overflow-y-auto">
<CommandGroup>
<TreeView
:data="data"
:selected-value="modelValue"
:on-fetch-children="onFetchChildren"
@select="handleSelect"
/>
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
</template>