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
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
<script setup lang="ts">
|
||||
import type { FormErrors } from '~/types/error'
|
||||
import AppDivisionEntryForm from '~/components/app/divison/entry-form.vue'
|
||||
import { division as divisionConf, schema as schemaConf } from './entry'
|
||||
|
||||
// Tipe data untuk tree item division
|
||||
interface DivisionTreeItem {
|
||||
value: string
|
||||
label: string
|
||||
code: string
|
||||
hasChildren: boolean
|
||||
children?: DivisionTreeItem[]
|
||||
}
|
||||
|
||||
// Props untuk komponen
|
||||
const props = defineProps<{
|
||||
initialValues?: {
|
||||
name: string
|
||||
code: string
|
||||
parentId: string
|
||||
}
|
||||
errors?: FormErrors
|
||||
}>()
|
||||
|
||||
// Events yang di-emit
|
||||
const emit = defineEmits<{
|
||||
'submit': [values: any, resetForm: () => void]
|
||||
'cancel': [resetForm: () => void]
|
||||
}>()
|
||||
|
||||
// State untuk tree data divisi - dimulai dengan data level atas
|
||||
const divisionTreeData = ref<DivisionTreeItem[]>([
|
||||
{ value: '1', label: 'Medical', code: 'MED', hasChildren: true },
|
||||
{ value: '2', label: 'Nursing', code: 'NUR', hasChildren: true },
|
||||
{ value: '3', label: 'Admin', code: 'ADM', hasChildren: false },
|
||||
{ value: '4', label: 'Support', code: 'SUP', hasChildren: true },
|
||||
{ value: '5', label: 'Education', code: 'EDU', hasChildren: false },
|
||||
{ value: '6', label: 'Pharmacy', code: 'PHA', hasChildren: true },
|
||||
{ value: '7', label: 'Radiology', code: 'RAD', hasChildren: false },
|
||||
{ value: '8', label: 'Laboratory', code: 'LAB', hasChildren: true },
|
||||
])
|
||||
|
||||
// Helper function untuk mencari dan menyisipkan data anak ke dalam tree
|
||||
function findAndInsertChildren(nodes: DivisionTreeItem[], parentId: string, newChildren: DivisionTreeItem[]): boolean {
|
||||
for (const node of nodes) {
|
||||
if (node.value === parentId) {
|
||||
node.children = newChildren
|
||||
return true
|
||||
}
|
||||
if (node.children && findAndInsertChildren(node.children, parentId, newChildren)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Fungsi untuk fetch data anak divisi (lazy loading)
|
||||
async function handleFetchDivisionChildren(parentId: string): Promise<void> {
|
||||
console.log(`Mengambil data sub-divisi untuk parent: ${parentId}`)
|
||||
|
||||
// Simulasi delay API call
|
||||
await new Promise(resolve => setTimeout(resolve, 800))
|
||||
|
||||
let childrenData: DivisionTreeItem[] = []
|
||||
|
||||
// Sample data berdasarkan parent ID
|
||||
switch (parentId) {
|
||||
case '1': // Medical
|
||||
childrenData = [
|
||||
{ value: '1-1', label: 'Cardiology', code: 'CAR', hasChildren: true },
|
||||
{ value: '1-2', label: 'Neurology', code: 'NEU', hasChildren: false },
|
||||
{ value: '1-3', label: 'Oncology', code: 'ONC', hasChildren: false },
|
||||
]
|
||||
break
|
||||
case '2': // Nursing
|
||||
childrenData = [
|
||||
{ value: '2-1', label: 'ICU Nursing', code: 'ICU-N', hasChildren: false },
|
||||
{ value: '2-2', label: 'ER Nursing', code: 'ER-N', hasChildren: false },
|
||||
{ value: '2-3', label: 'Ward Nursing', code: 'WARD-N', hasChildren: true },
|
||||
]
|
||||
break
|
||||
case '4': // Support
|
||||
childrenData = [
|
||||
{ value: '4-1', label: 'IT Support', code: 'IT-S', hasChildren: false },
|
||||
{ value: '4-2', label: 'Maintenance', code: 'MNT-S', hasChildren: false },
|
||||
]
|
||||
break
|
||||
case '6': // Pharmacy
|
||||
childrenData = [
|
||||
{ value: '6-1', label: 'Inpatient Pharmacy', code: 'INP-PHA', hasChildren: false },
|
||||
{ value: '6-2', label: 'Outpatient Pharmacy', code: 'OUT-PHA', hasChildren: false },
|
||||
]
|
||||
break
|
||||
case '8': // Laboratory
|
||||
childrenData = [
|
||||
{ value: '8-1', label: 'Clinical Lab', code: 'CLI-LAB', hasChildren: false },
|
||||
{ value: '8-2', label: 'Pathology Lab', code: 'PAT-LAB', hasChildren: false },
|
||||
]
|
||||
break
|
||||
case '1-1': // Cardiology sub-divisions
|
||||
childrenData = [
|
||||
{ value: '1-1-1', label: 'Cardiac Surgery', code: 'CAR-SUR', hasChildren: false },
|
||||
{ value: '1-1-2', label: 'Cardiac Cathlab', code: 'CAR-CAT', hasChildren: false },
|
||||
]
|
||||
break
|
||||
case '2-3': // Ward Nursing sub-divisions
|
||||
childrenData = [
|
||||
{ value: '2-3-1', label: 'Pediatric Ward', code: 'PED-W', hasChildren: false },
|
||||
{ value: '2-3-2', label: 'Surgical Ward', code: 'SUR-W', hasChildren: false },
|
||||
]
|
||||
break
|
||||
}
|
||||
|
||||
// Insert data ke dalam tree state
|
||||
findAndInsertChildren(divisionTreeData.value, parentId, childrenData)
|
||||
}
|
||||
|
||||
// Configuration untuk tree select
|
||||
const divisionTreeConfig = computed(() => ({
|
||||
msg: {
|
||||
placeholder: '--- Pilih divisi induk',
|
||||
search: 'Cari divisi...',
|
||||
empty: 'Divisi tidak ditemukan',
|
||||
},
|
||||
data: divisionTreeData.value,
|
||||
onFetchChildren: handleFetchDivisionChildren,
|
||||
}))
|
||||
|
||||
// Event handlers
|
||||
function onSubmitForm(values: any, resetForm: () => void) {
|
||||
emit('submit', values, resetForm)
|
||||
}
|
||||
|
||||
function onCancelForm(resetForm: () => void) {
|
||||
emit('cancel', resetForm)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mb-5 border-b border-b-slate-300 pb-3 text-lg xl:text-xl">
|
||||
<Icon name="i-lucide-sitemap" class="me-2" />
|
||||
<span class="font-semibold">Tambah</span> Divisi
|
||||
</div>
|
||||
|
||||
<AppDivisionEntryForm
|
||||
:division="divisionConf"
|
||||
:division-tree="divisionTreeConfig"
|
||||
:schema="schemaConf"
|
||||
:initial-values="initialValues || { name: '', code: '', parentId: '' }"
|
||||
:errors="errors"
|
||||
@submit="onSubmitForm"
|
||||
@cancel="onCancelForm"
|
||||
/>
|
||||
</template>
|
||||
@@ -1,12 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import type { HeaderPrep } from '~/components/pub/custom-ui/data/types'
|
||||
import AppDivisonEntryForm from '~/components/app/divison/entry-form.vue'
|
||||
import AppDivisionEntry from './entry.vue'
|
||||
import Dialog from '~/components/pub/base/modal/dialog.vue'
|
||||
import RecordConfirmation from '~/components/pub/custom-ui/confirmation/record-confirmation.vue'
|
||||
import { ActionEvents } from '~/components/pub/custom-ui/data/types'
|
||||
import Header from '~/components/pub/custom-ui/nav-header/header.vue'
|
||||
import { usePaginatedList } from '~/composables/usePaginatedList'
|
||||
import { division as divisionConf, schema as schemaConf } from './entry'
|
||||
|
||||
// #region State & Computed
|
||||
// Dialog state
|
||||
@@ -30,7 +29,8 @@ async function fetchDivisionData(params: any) {
|
||||
urlParams.append('search', params.q)
|
||||
}
|
||||
|
||||
return await xfetch(`/api/v1/patient?${urlParams.toString()}`)
|
||||
// return await xfetch(`/api/v1/patient?${urlParams.toString()}`)
|
||||
return await xfetch('/api/v1/_dev/division/list')
|
||||
}
|
||||
|
||||
// Menggunakan composable untuk pagination
|
||||
@@ -192,10 +192,11 @@ function handleCancelConfirmation() {
|
||||
<AppDivisonList :data="data" :pagination-meta="paginationMeta" @page-change="handlePageChange" />
|
||||
|
||||
<Dialog v-model:open="isFormEntryDialogOpen" title="Tambah Divisi" size="lg" prevent-outside>
|
||||
<AppDivisonEntryForm
|
||||
:division="divisionConf" :schema="schemaConf"
|
||||
:initial-values="{ name: '', code: '', parentId: '' }" @submit="onSubmitForm" @cancel="onCancelForm"
|
||||
/>
|
||||
<AppDivisionEntry
|
||||
:initial-values="{ name: '', code: '', parentId: '' }"
|
||||
@submit="onSubmitForm"
|
||||
@cancel="onCancelForm"
|
||||
/>
|
||||
</Dialog>
|
||||
|
||||
<!-- Record Confirmation Modal -->
|
||||
|
||||
Reference in New Issue
Block a user