From ba6485a3e7812faa8b22fbea916fa47ddc6c978e Mon Sep 17 00:00:00 2001 From: Khafid Prayoga Date: Tue, 9 Sep 2025 13:37:52 +0700 Subject: [PATCH] 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 --- app/components/app/divison/entry-form.vue | 28 ++- app/components/app/divison/list-cfg.ts | 47 +---- app/components/app/divison/tree.vue | 165 ++++++++++++++++++ app/components/content/division/entry.vue | 154 ++++++++++++++++ app/components/content/division/list.vue | 15 +- .../pub/base/select-tree/command-item.vue | 27 +++ app/components/pub/base/select-tree/leaf.vue | 40 +++++ .../pub/base/select-tree/tree-node.vue | 121 +++++++++++++ .../pub/base/select-tree/tree-select.vue | 68 ++++++++ .../pub/base/select-tree/tree-view.vue | 45 +++++ app/components/pub/base/select-tree/type.ts | 6 + app/pages/_dev/user/list.vue | 74 +++++++- server/api/v1/_dev/division/list.get.ts | 163 +++++++++++++++++ 13 files changed, 902 insertions(+), 51 deletions(-) create mode 100644 app/components/app/divison/tree.vue create mode 100644 app/components/content/division/entry.vue create mode 100644 app/components/pub/base/select-tree/command-item.vue create mode 100644 app/components/pub/base/select-tree/leaf.vue create mode 100644 app/components/pub/base/select-tree/tree-node.vue create mode 100644 app/components/pub/base/select-tree/tree-select.vue create mode 100644 app/components/pub/base/select-tree/tree-view.vue create mode 100644 app/components/pub/base/select-tree/type.ts create mode 100644 server/api/v1/_dev/division/list.get.ts diff --git a/app/components/app/divison/entry-form.vue b/app/components/app/divison/entry-form.vue index 3008d3c8..1780895a 100644 --- a/app/components/app/divison/entry-form.vue +++ b/app/components/app/divison/entry-form.vue @@ -1,6 +1,8 @@ + + diff --git a/app/components/content/division/entry.vue b/app/components/content/division/entry.vue new file mode 100644 index 00000000..a6ccf816 --- /dev/null +++ b/app/components/content/division/entry.vue @@ -0,0 +1,154 @@ + + + diff --git a/app/components/content/division/list.vue b/app/components/content/division/list.vue index f4bf78e6..511a5a1a 100644 --- a/app/components/content/division/list.vue +++ b/app/components/content/division/list.vue @@ -1,12 +1,11 @@ + + diff --git a/app/components/pub/base/select-tree/leaf.vue b/app/components/pub/base/select-tree/leaf.vue new file mode 100644 index 00000000..1dc8c892 --- /dev/null +++ b/app/components/pub/base/select-tree/leaf.vue @@ -0,0 +1,40 @@ + + + + + diff --git a/app/components/pub/base/select-tree/tree-node.vue b/app/components/pub/base/select-tree/tree-node.vue new file mode 100644 index 00000000..da7a0bdf --- /dev/null +++ b/app/components/pub/base/select-tree/tree-node.vue @@ -0,0 +1,121 @@ + + + + + diff --git a/app/components/pub/base/select-tree/tree-select.vue b/app/components/pub/base/select-tree/tree-select.vue new file mode 100644 index 00000000..3a7cafd8 --- /dev/null +++ b/app/components/pub/base/select-tree/tree-select.vue @@ -0,0 +1,68 @@ + + + diff --git a/app/components/pub/base/select-tree/tree-view.vue b/app/components/pub/base/select-tree/tree-view.vue new file mode 100644 index 00000000..abe6c0de --- /dev/null +++ b/app/components/pub/base/select-tree/tree-view.vue @@ -0,0 +1,45 @@ + + + diff --git a/app/components/pub/base/select-tree/type.ts b/app/components/pub/base/select-tree/type.ts new file mode 100644 index 00000000..37d16761 --- /dev/null +++ b/app/components/pub/base/select-tree/type.ts @@ -0,0 +1,6 @@ +export interface TreeItem { + value: string + label: string + hasChildren: boolean + children?: TreeItem[] +} diff --git a/app/pages/_dev/user/list.vue b/app/pages/_dev/user/list.vue index 4c5edc33..ae207f41 100644 --- a/app/pages/_dev/user/list.vue +++ b/app/pages/_dev/user/list.vue @@ -1,9 +1,81 @@ diff --git a/server/api/v1/_dev/division/list.get.ts b/server/api/v1/_dev/division/list.get.ts new file mode 100644 index 00000000..377e1592 --- /dev/null +++ b/server/api/v1/_dev/division/list.get.ts @@ -0,0 +1,163 @@ +export default defineEventHandler(async (event) => { + // Ambil query parameters + const payload = { ...getQuery(event) } + const isTreeFormat = payload.tree === 'true' || payload.tree === '1' + + // Mock data division dengan struktur nested meta parent + const baseDivisions = [ + { + id: 1, + name: 'Direktorat Medis', + code: 'DIR-MED', + parentId: null, + }, + { + id: 2, + name: 'Bidang Medik', + code: 'BDG-MED', + parentId: 1, + }, + { + id: 3, + name: 'Tim Kerja Ranap, ICU, Bedah', + code: 'TIM-RAN', + parentId: 2, + }, + { + id: 4, + name: 'Direktorat Keperawatan', + code: 'DIR-KEP', + parentId: null, + }, + { + id: 5, + name: 'Bidang Keperawatan', + code: 'BDG-KEP', + parentId: 4, + }, + { + id: 6, + name: 'Tim Kerja Keperawatan Ranap, ICU, Bedah', + code: 'TIM-KEP', + parentId: 5, + }, + { + id: 7, + name: 'Direktorat Penunjang', + code: 'DIR-PNJ', + parentId: null, + }, + { + id: 8, + name: 'Bidang Penunjang Medik', + code: 'BDG-PNJ', + parentId: 7, + }, + { + id: 9, + name: 'Tim Kerja Radiologi', + code: 'TIM-RAD', + parentId: 8, + }, + { + id: 10, + name: 'Direktorat Produksi', + code: 'DIR-PRD', + parentId: null, + }, + { + id: 11, + name: 'Bidang Teknologi', + code: 'BDG-TEK', + parentId: 10, + }, + { + id: 12, + name: 'Tim Kerja Software Engineering', + code: 'TIM-SWE', + parentId: 11, + }, + { + id: 13, + name: 'Direktorat Operasional', + code: 'DIR-OPS', + parentId: null, + }, + { + id: 14, + name: 'Bidang HR & GA', + code: 'BDG-HRG', + parentId: 13, + }, + { + id: 15, + name: 'Tim Kerja Rekrutmen', + code: 'TIM-REC', + parentId: 14, + }, + ] + + // Menambahkan meta parent pada setiap division + const divisions = baseDivisions + .map((division) => { + const parent = baseDivisions.find((d) => d.id === division.parentId) + + const mapped = { + ...division, + meta: { + parentId: parent?.id || null, + name: parent?.name || null, + code: parent?.code || null, + }, + } + + if (mapped.meta.parentId === null) { + mapped.meta = null + } + + return mapped + }) + .sort((a, b) => { + if (a.parentId === null && b.parentId !== null) return -1 + if (a.parentId !== null && b.parentId === null) return 1 + + return a.id - b.id + }) + + // Jika tree format diminta, konversi ke struktur hierarki + if (isTreeFormat) { + const buildTree = (parentId = null) => { + return baseDivisions + .filter(division => division.parentId === parentId) + .map(division => ({ + id: division.id, + name: division.name, + code: division.code, + children: buildTree(division.id), + })) + .sort((a, b) => a.id - b.id) + } + + const treeData = buildTree() + + return { + success: true, + data: treeData, + message: 'Data division dalam format tree berhasil diambil', + meta: { + record_totalCount: baseDivisions.length, + format: 'tree', + }, + } + } + + return { + success: true, + data: divisions, + message: 'Data division berhasil diambil', + meta: { + record_totalCount: divisions.length, + format: 'flat', + }, + } +})