Files
simrsx-fe/app/components/pub/my-ui/select-tree/tree-node.vue
Muhammad Rifai f94b6d273a Feat Infra (#108)
* fix: adjustment some schemas

* fix(room): fixing integrate unit of room

* feat(warehouse): modify form and integration

* feat(counter): modify form and integration

* feat(screen): add list, form and integration

* feat(screen): add page for public screen

* fix: add on reset state at list

* fix: solve list of relation

* feat(chamber): integrate form to api chamber

* feat(bed): integrate form to api bed

* fix: add searching function on list service

* fix: rewrite style for dropdown and tree select

* fix: add sort params

* fix: add sort params on division + medicine

* feat(division-position): layouting form + list

* fix: add sort params for getValueList

* chore: modify side menu style

* chore: fix ui dashboard

* feat(division-position): add content list

* feat(division-position): add temporary page

* feat(division-position): modify content and entry form
2025-10-10 20:36:07 +07:00

128 lines
3.5 KiB
Vue

<script setup lang="ts">
import type { TreeItem } from './type'
import { Check, ChevronRight, Loader2 } from 'lucide-vue-next'
import TreeView from './tree-view.vue'
const props = defineProps<{
item: TreeItem
selectedValue?: string
onFetchChildren: (parentId: string) => Promise<void>
level?: number
}>()
const emit = defineEmits(['select'])
const hasChildren = computed(() => props.item.children && props.item.children.length > 0)
const isOpen = ref(false)
const isLoading = ref(false)
const isChevronRotated = ref(false)
function handleSelect(value: string) {
emit('select', value)
}
function handleLabelClick() {
handleSelect(props.item.value)
}
watch(isOpen, async (newValue) => {
console.log(`[TreeNode] ${props.item.label} - isOpen changed to:`, newValue)
isChevronRotated.value = newValue
if (newValue && props.item.hasChildren && !props.item.children && !isLoading.value) {
console.log(`[TreeNode] Fetching children for: ${props.item.label}`)
isLoading.value = true
try {
await props.onFetchChildren(props.item.value)
console.log(`[TreeNode] Fetch completed for: ${props.item.label}`, props.item.children)
// Force reactivity update dengan nextTick
await nextTick()
} catch (error) {
console.error('Gagal memuat data anak:', error)
// Tutup kembali jika gagal fetch
isOpen.value = false
} finally {
isLoading.value = false
}
}
})
</script>
<template>
<div class="tree-node min-w-max">
<Collapsible
v-model:open="isOpen"
class="w-full"
>
<!-- Node Header -->
<div
class="flex w-full items-center justify-start gap-2 rounded-md bg-white p-2 hover:bg-accent dark:bg-transparent dark:hover:bg-slate-700"
>
<!-- Chevron Toggle Button -->
<CollapsibleTrigger as-child>
<Button
variant="ghost"
class="flex h-4 w-4 items-center justify-center p-0"
>
<Loader2
v-if="isLoading"
class="h-4 w-4 animate-spin text-muted-foreground"
/>
<ChevronRight
v-else
class="h-4 w-4 text-muted-foreground transition-transform duration-200 ease-in-out"
:class="{
'rotate-90': isChevronRotated,
}"
/>
</Button>
</CollapsibleTrigger>
<!-- Node Label -->
<span
class="flex flex-1 cursor-pointer items-center justify-between text-sm font-normal text-black hover:text-primary dark:text-white"
@click="handleLabelClick"
>
{{ item.label }}
<!-- Check Icon untuk selected state -->
<Check
v-if="selectedValue === item.value"
class="ml-2 h-4 w-4 flex-shrink-0 text-primary"
/>
</span>
</div>
<!-- Children Container -->
<CollapsibleContent class="pl-6">
<div
v-if="!hasChildren"
class="p-2 text-sm text-muted-foreground"
>
{{ isLoading ? 'Memuat...' : 'Tidak ada data' }}
</div>
<TreeView
v-else
:data="item.children!"
:selected-value="selectedValue"
:on-fetch-children="onFetchChildren"
:level="(level || 0) + 1"
@select="handleSelect"
/>
</CollapsibleContent>
</Collapsible>
</div>
</template>
<style scoped>
.tree-node {
width: 100%;
}
/* Animasi tambahan untuk smooth transition */
.tree-node .collapsible-content {
transition: all 0.2s ease-in-out;
}
</style>