diff --git a/app/components/app/divison/entry-form.vue b/app/components/app/divison/entry-form.vue index 3008d3c8..7f21d90a 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.ts b/app/components/content/division/entry.ts index 60617c45..aef024e7 100644 --- a/app/components/content/division/entry.ts +++ b/app/components/content/division/entry.ts @@ -1,58 +1,145 @@ +import type { TreeItem } from '~/components/pub/base/select-tree/type' import * as z from 'zod' -export const division = { +export const divisionConf = { msg: { placeholder: '---pilih divisi utama', search: 'kode, nama divisi', empty: 'divisi tidak ditemukan', }, items: [ - { value: '1', label: 'Medical', code: 'MED' }, - { value: '2', label: 'Nursing', code: 'NUR' }, - { value: '3', label: 'Admin', code: 'ADM' }, - { value: '4', label: 'Support', code: 'SUP' }, - { value: '5', label: 'Education', code: 'EDU' }, - { value: '6', label: 'Pharmacy', code: 'PHA' }, - { value: '7', label: 'Radiology', code: 'RAD' }, - { value: '8', label: 'Laboratory', code: 'LAB' }, - { value: '9', label: 'Finance', code: 'FIN' }, - { value: '10', label: 'Human Resources', code: 'HR' }, - { value: '11', label: 'IT Services', code: 'ITS' }, - { value: '12', label: 'Maintenance', code: 'MNT' }, - { value: '13', label: 'Catering', code: 'CAT' }, - { value: '14', label: 'Security', code: 'SEC' }, - { value: '15', label: 'Emergency', code: 'EMR' }, - { value: '16', label: 'Surgery', code: 'SUR' }, - { value: '17', label: 'Outpatient', code: 'OUT' }, - { value: '18', label: 'Inpatient', code: 'INP' }, - { value: '19', label: 'Rehabilitation', code: 'REB' }, - { value: '20', label: 'Research', code: 'RSH' }, + { value: '1', label: 'Medical' }, + { value: '2', label: 'Nursing' }, + { value: '3', label: 'Admin' }, + { value: '4', label: 'Support' }, + { value: '5', label: 'Education' }, + { value: '6', label: 'Pharmacy' }, + { value: '7', label: 'Radiology' }, + { value: '8', label: 'Laboratory' }, + { value: '9', label: 'Finance' }, + { value: '10', label: 'Human Resources' }, + { value: '11', label: 'IT Services' }, + { value: '12', label: 'Maintenance' }, + { value: '13', label: 'Catering' }, + { value: '14', label: 'Security' }, + { value: '15', label: 'Emergency' }, + { value: '16', label: 'Surgery' }, + { value: '17', label: 'Outpatient' }, + { value: '18', label: 'Inpatient' }, + { value: '19', label: 'Rehabilitation' }, + { value: '20', label: 'Research' }, ], } export const schema = z.object({ - name: z.string({ - required_error: 'Nama wajib diisi', - }).min(1, 'Nama divisi wajib diisi'), + name: z + .string({ + required_error: 'Nama wajib diisi', + }) + .min(1, 'Nama divisi wajib diisi'), - code: z.string({ - required_error: 'Kode wajib diisi', - }).min(1, 'Kode divisi wajib diisi'), + code: z + .string({ + required_error: 'Kode wajib diisi', + }) + .min(1, 'Kode divisi wajib diisi'), - parentId: z.preprocess( - (input: unknown) => { - if (typeof input === 'string') { - // Handle empty string case - if (input.trim() === '') { - return undefined - } - return Number(input) - } - - return input - }, - z.number({ - required_error: 'Kelompok wajib dipilih', - }).min(1, 'Kelompok wajib dipilih'), - ), + parentId: z.string().optional(), }) + +// State untuk tree data divisi - dimulai dengan data level atas +const divisionTreeData = ref([ + { value: '1', label: 'Medical', hasChildren: true }, + { value: '2', label: 'Nursing', hasChildren: true }, + { value: '3', label: 'Admin', hasChildren: false }, + { value: '4', label: 'Support', hasChildren: true }, + { value: '5', label: 'Education', hasChildren: false }, + { value: '6', label: 'Pharmacy', hasChildren: true }, + { value: '7', label: 'Radiology', hasChildren: false }, + { value: '8', label: 'Laboratory', hasChildren: true }, +]) + +// Helper function untuk mencari dan menyisipkan data anak ke dalam tree +function findAndInsertChildren(nodes: TreeItem[], parentId: string, newChildren: TreeItem[]): boolean { + for (const node of nodes) { + if (node.value === parentId) { + node.children = newChildren + return true + } + if (node.children && findAndInsertChildren(node.children as TreeItem[], parentId, newChildren)) { + return true + } + } + return false +} + +// Fungsi untuk fetch data anak divisi (lazy loading) +async function handleFetchDivisionChildren(parentId: string): Promise { + console.log(`Mengambil data sub-divisi untuk parent: ${parentId}`) + + // Simulasi delay API call + await new Promise((resolve) => setTimeout(resolve, 800)) + + let childrenData: TreeItem[] = [] + + // Sample data berdasarkan parent ID + switch (parentId) { + case '1': // Medical + childrenData = [ + { value: '1-1', label: 'Cardiology', hasChildren: true }, + { value: '1-2', label: 'Neurology', hasChildren: false }, + { value: '1-3', label: 'Oncology', hasChildren: false }, + ] + break + case '2': // Nursing + childrenData = [ + { value: '2-1', label: 'ICU Nursing', hasChildren: false }, + { value: '2-2', label: 'ER Nursing', hasChildren: false }, + { value: '2-3', label: 'Ward Nursing', hasChildren: true }, + ] + break + case '4': // Support + childrenData = [ + { value: '4-1', label: 'IT Support', hasChildren: false }, + { value: '4-2', label: 'Maintenance', hasChildren: false }, + ] + break + case '6': // Pharmacy + childrenData = [ + { value: '6-1', label: 'Inpatient Pharmacy', hasChildren: false }, + { value: '6-2', label: 'Outpatient Pharmacy', hasChildren: false }, + ] + break + case '8': // Laboratory + childrenData = [ + { value: '8-1', label: 'Clinical Lab', hasChildren: false }, + { value: '8-2', label: 'Pathology Lab', hasChildren: false }, + ] + break + case '1-1': // Cardiology sub-divisions + childrenData = [ + { value: '1-1-1', label: 'Cardiac Surgery', hasChildren: false }, + { value: '1-1-2', label: 'Cardiac Cathlab', hasChildren: false }, + ] + break + case '2-3': // Ward Nursing sub-divisions + childrenData = [ + { value: '2-3-1', label: 'Pediatric Ward', hasChildren: false }, + { value: '2-3-2', label: 'Surgical Ward', hasChildren: false }, + ] + break + } + + // Insert data ke dalam tree state + findAndInsertChildren(divisionTreeData.value, parentId, childrenData) +} + +export const divisionTreeConfig = computed(() => ({ + msg: { + placeholder: '--- Pilih divisi induk', + search: 'Cari divisi...', + empty: 'Divisi tidak ditemukan', + }, + data: divisionTreeData.value, + onFetchChildren: handleFetchDivisionChildren, +})) diff --git a/app/components/content/division/list.vue b/app/components/content/division/list.vue index f6a3efc7..d77b67d3 100644 --- a/app/components/content/division/list.vue +++ b/app/components/content/division/list.vue @@ -1,13 +1,12 @@ + + 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..81dabf2b --- /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..e390b7bb --- /dev/null +++ b/app/components/pub/base/select-tree/tree-node.vue @@ -0,0 +1,116 @@ + + + + + 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..d0525336 --- /dev/null +++ b/app/components/pub/base/select-tree/tree-select.vue @@ -0,0 +1,69 @@ + + + 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..dfc51d5c --- /dev/null +++ b/app/components/pub/base/select-tree/tree-view.vue @@ -0,0 +1,55 @@ + + + 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', + }, + } +})