refactor(division): move entry logic to shared config file

Extract division form configuration and tree logic from entry.vue component into a shared entry.ts file. This improves code organization and reusability while maintaining the same functionality.
This commit is contained in:
Khafid Prayoga
2025-09-16 15:38:06 +07:00
parent c5ec8ccd32
commit 0ae1923f27
4 changed files with 142 additions and 205 deletions
@@ -23,7 +23,6 @@ const props = defineProps<{
items: {
value: string
label: string
code: string
}[]
}
divisionTree?: {
+130 -43
View File
@@ -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<TreeItem[]>([
{ 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<void> {
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,
}))
-154
View File
@@ -1,154 +0,0 @@
<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>
+12 -7
View File
@@ -1,12 +1,12 @@
<script setup lang="ts">
import type { HeaderPrep } from '~/components/pub/custom-ui/data/types'
import AppDivisionEntry from './entry.vue'
import AppDivisionEntryForm from '~/components/app/divison/entry-form.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 { divisionConf, divisionTreeConfig, schema } from './entry'
// #region State & Computed
// Dialog state
const isFormEntryDialogOpen = ref(false)
@@ -192,11 +192,16 @@ function handleCancelConfirmation() {
<AppDivisonList :data="data" :pagination-meta="paginationMeta" @page-change="handlePageChange" />
<Dialog v-model:open="isFormEntryDialogOpen" title="Tambah Divisi" size="lg" prevent-outside>
<AppDivisionEntry
:initial-values="{ name: '', code: '', parentId: '' }"
@submit="onSubmitForm"
@cancel="onCancelForm"
/>
<AppDivisionEntryForm
:division="divisionConf"
:division-tree="divisionTreeConfig"
:schema="schema"
:initial-values="{ name: '', code: '', parentId: '' }"
@submit="onSubmitForm"
@cancel="onCancelForm"
/>
</Dialog>
<!-- Record Confirmation Modal -->