fix: update content list of specialist, subspecialist, etc

This commit is contained in:
riefive
2025-10-06 11:06:29 +07:00
parent 301cb82803
commit fff5f2c11d
9 changed files with 34 additions and 718 deletions
@@ -1,240 +0,0 @@
<script setup lang="ts">
import type { HeaderPrep } from '~/components/pub/my-ui/data/types'
import AppSpecialistEntryForm from '~/components/app/specialist/entry-form.vue'
import Dialog from '~/components/pub/my-ui/modal/dialog.vue'
import RecordConfirmation from '~/components/pub/my-ui/confirmation/record-confirmation.vue'
import { ActionEvents } from '~/components/pub/my-ui/data/types'
import Header from '~/components/pub/my-ui/nav-header/header.vue'
import { usePaginatedList } from '~/composables/usePaginatedList'
import { createFilteredUnitConf, installationConf, schemaConf, unitConf } from './entry'
// #region State & Computed
// Dialog state
const isFormEntryDialogOpen = ref(false)
const isRecordConfirmationOpen = ref(false)
// Table action rowId provider
const recId = ref<number>(0)
const recAction = ref<string>('')
const recItem = ref<any>(null)
// State untuk tracking installation yang dipilih di form
const selectedInstallationId = ref<string>('')
// Computed untuk filtered unit berdasarkan installation yang dipilih
const filteredUnitConf = computed(() => {
if (!selectedInstallationId.value) {
return { ...unitConf, items: [] }
}
return createFilteredUnitConf(selectedInstallationId.value)
})
const {
data,
isLoading,
paginationMeta,
searchInput,
handlePageChange,
handleSearch,
fetchData: getSpecialistList,
} = usePaginatedList({
fetchFn: fetchSpecialistData,
entityName: 'specialist',
})
const headerPrep: HeaderPrep = {
title: 'Specialist',
icon: 'i-lucide-box',
refSearchNav: {
placeholder: 'Cari (min. 3 karakter)...',
minLength: 3,
debounceMs: 500,
showValidationFeedback: true,
onInput: (_val: string) => {
// Handle search input - this will be triggered by the header component
},
onClick: () => {
// Handle search button click if needed
},
onClear: () => {
// Handle search clear
},
},
addNav: {
label: 'Tambah Specialist',
icon: 'i-lucide-send',
onClick: () => {
isFormEntryDialogOpen.value = true
},
},
}
provide('rec_id', recId)
provide('rec_action', recAction)
provide('rec_item', recItem)
provide('table_data_loader', isLoading)
// #endregion
// #region Functions
async function fetchSpecialistData(params: any) {
const endpoint = transform('/api/v1/patient', params)
return await xfetch(endpoint)
}
async function handleDeleteRow(record: any) {
try {
// TODO : hit backend request untuk delete
console.log('Deleting record:', record)
// Simulate API call
// const response = await xfetch(`/api/v1/Installation/${record.id}`, {
// method: 'DELETE'
// })
// Refresh data setelah berhasil delete
await getSpecialistList()
// TODO: Show success message
console.log('Record deleted successfully')
} catch (error) {
console.error('Error deleting record:', error)
// TODO: Show error message
} finally {
// Reset record state
recId.value = 0
recAction.value = ''
recItem.value = null
}
}
// Handle confirmation result
function handleConfirmDelete(record: any, action: string) {
console.log('Confirmed action:', action, 'for record:', record)
handleDeleteRow(record)
}
function handleCancelConfirmation() {
// Reset record state when cancelled
recId.value = 0
recAction.value = ''
recItem.value = null
}
// #endregion region
// #region Form event handlers
function onCancelForm(resetForm: () => void) {
isFormEntryDialogOpen.value = false
// Reset installation selection ketika form dibatal
selectedInstallationId.value = ''
setTimeout(() => {
resetForm()
}, 500)
}
function onInstallationChanged(installationId: string) {
// Update local state untuk trigger re-render filtered units
selectedInstallationId.value = installationId
// The filteredUnitConf computed will automatically update
// based on the new selectedInstallationId value
}
async function onSubmitForm(values: any, resetForm: () => void) {
let isSuccess = false
try {
// TODO: Implement form submission logic
console.log('Form submitted:', values)
// Simulate API call
// const response = await xfetch('/api/v1/Installation', {
// method: 'POST',
// body: JSON.stringify(values)
// })
// If successful, mark as success and close dialog
isFormEntryDialogOpen.value = false
isSuccess = true
// Reset installation selection ketika form berhasil submit
selectedInstallationId.value = ''
// Refresh data after successful submission
await getSpecialistList()
// TODO: Show success message
console.log('Installation created successfully')
} catch (error: unknown) {
console.warn('Error submitting form:', error)
isSuccess = false
// Don't close dialog or reset form on error
// TODO: Show error message to user
} finally {
if (isSuccess) {
setTimeout(() => {
resetForm()
}, 500)
}
}
}
// #endregion
// #region Watchers
// Watch for row actions
watch(recId, () => {
switch (recAction.value) {
case ActionEvents.showEdit:
// TODO: Handle edit action
// isFormEntryDialogOpen.value = true
break
case ActionEvents.showConfirmDelete:
// Trigger confirmation modal open
isRecordConfirmationOpen.value = true
break
}
})
// Note: Installation change logic is now handled by the entry-form component
// through the onInstallationChanged event handler
// #endregion
</script>
<template>
<Header v-model="searchInput" :prep="headerPrep" @search="handleSearch" />
<AppSpecialistList :data="data" :pagination-meta="paginationMeta" @page-change="handlePageChange" />
<Dialog v-model:open="isFormEntryDialogOpen" :title="headerPrep.addNav?.label!" size="lg" prevent-outside>
<AppSpecialistEntryFormPrev
:installation="installationConf"
:unit="filteredUnitConf"
:schema="schemaConf"
:disabled-unit="!selectedInstallationId"
:initial-values="{ name: '', code: '', installationId: '', unitId: '' }"
@submit="onSubmitForm"
@cancel="onCancelForm"
@installation-changed="onInstallationChanged"
/>
</Dialog>
<RecordConfirmation
v-model:open="isRecordConfirmationOpen"
action="delete"
:record="recItem"
@confirm="handleConfirmDelete"
@cancel="handleCancelConfirmation"
>
<template #default="{ record }">
<div class="text-sm">
<p><strong>ID:</strong> {{ record?.id }}</p>
<p v-if="record?.firstName"><strong>Nama:</strong> {{ record.firstName }}</p>
<p v-if="record?.code"><strong>Kode:</strong> {{ record.cellphone }}</p>
</div>
</template>
</RecordConfirmation>
</template>
<style scoped>
/* component style */
</style>
+8 -8
View File
@@ -28,12 +28,12 @@ import {
handleActionRemove,
handleCancelForm,
} from '~/handlers/specialist.handler'
import { units, getUnitList } from '~/handlers/_shared.handler'
// Services
import { getSpecialists, getSpecialistDetail } from '~/services/specialist.service'
import { get } from "@vueuse/core"
import { getList, getDetail } from '~/services/specialist.service'
import { getValueLabelList as getUnitList } from '~/services/unit.service'
const units = ref<{ value: string | number; label: string }[]>([])
const title = ref('')
const {
@@ -46,10 +46,10 @@ const {
fetchData: getSpecialistList,
} = usePaginatedList({
fetchFn: async (params: any) => {
// TODO: use pagination params
const result = await getSpecialists({
const result = await getList({
search: params.search,
page: params['page-number'] || 0,
'page-number': params['page-number'] || 0,
'page-size': params['page-size'] || 10,
includes: 'unit',
})
return { success: result.success || false, body: result.body || {} }
@@ -89,7 +89,7 @@ provide('rec_item', recItem)
provide('table_data_loader', isLoading)
const getCurrentSpecialistDetail = async (id: number | string) => {
const result = await getSpecialistDetail(id)
const result = await getDetail(id)
if (result.success) {
const currentValue = result.body?.data || {}
recItem.value = currentValue
@@ -117,8 +117,8 @@ watch([recId, recAction], () => {
})
onMounted(async () => {
units.value = await getUnitList()
await getSpecialistList()
await getUnitList()
})
</script>
@@ -1,239 +0,0 @@
<script setup lang="ts">
import type { HeaderPrep } from '~/components/pub/my-ui/data/types'
import AppSubspecialistEntryForm from '~/components/app/subspecialist/entry-form.vue'
import Dialog from '~/components/pub/my-ui/modal/dialog.vue'
import RecordConfirmation from '~/components/pub/my-ui/confirmation/record-confirmation.vue'
import { ActionEvents } from '~/components/pub/my-ui/data/types'
import Header from '~/components/pub/my-ui/nav-header/header.vue'
import { usePaginatedList } from '~/composables/usePaginatedList'
import { createFilteredUnitConf, installationConf, schemaConf, specialistConf, unitConf } from './entry'
// #region State & Computed
// Dialog state
const isFormEntryDialogOpen = ref(false)
const isRecordConfirmationOpen = ref(false)
// Table action rowId provider
const recId = ref<number>(0)
const recAction = ref<string>('')
const recItem = ref<any>(null)
// State untuk tracking installation, unit, dan specialist yang dipilih di form
const selectedInstallationId = ref<string>('')
const selectedUnitId = ref<string>('')
// Computed untuk filtered unit berdasarkan installation yang dipilih
const filteredUnitConf = computed(() => {
if (!selectedInstallationId.value) {
return { ...unitConf, items: [] }
}
return createFilteredUnitConf(selectedInstallationId.value)
})
const {
data,
isLoading,
paginationMeta,
searchInput,
handlePageChange,
handleSearch,
fetchData: getSubSpecialistList,
} = usePaginatedList({
fetchFn: fetchSubSpecialistData,
entityName: 'subspecialist',
})
const headerPrep: HeaderPrep = {
title: 'Sub Specialist',
icon: 'i-lucide-box',
refSearchNav: {
placeholder: 'Cari (min. 3 karakter)...',
minLength: 3,
debounceMs: 500,
showValidationFeedback: true,
onInput: (_val: string) => {
// Handle search input - this will be triggered by the header component
},
onClick: () => {
// Handle search button click if needed
},
onClear: () => {
// Handle search clear
},
},
addNav: {
label: 'Tambah Sub Specialist',
icon: 'i-lucide-send',
onClick: () => {
isFormEntryDialogOpen.value = true
},
},
}
provide('rec_id', recId)
provide('rec_action', recAction)
provide('rec_item', recItem)
provide('table_data_loader', isLoading)
// #endregion
// #region Functions
async function fetchSubSpecialistData(params: any) {
const endpoint = transform('/api/v1/patient', params)
return await xfetch(endpoint)
}
async function handleDeleteRow(record: any) {
try {
// TODO : hit backend request untuk delete
console.log('Deleting record:', record)
// Simulate API call
// const response = await xfetch(`/api/v1/subspecialist/${record.id}`, {
// method: 'DELETE'
// })
// Refresh data setelah berhasil delete
await getSubSpecialistList()
// TODO: Show success message
console.log('Record deleted successfully')
} catch (error) {
console.error('Error deleting record:', error)
// TODO: Show error message
} finally {
// Reset record state
recId.value = 0
recAction.value = ''
recItem.value = null
}
}
// Handle confirmation result
function handleConfirmDelete(record: any, action: string) {
console.log('Confirmed action:', action, 'for record:', record)
handleDeleteRow(record)
}
function handleCancelConfirmation() {
// Reset record state when cancelled
recId.value = 0
recAction.value = ''
recItem.value = null
}
// #endregion region
// #region Form event handlers
function onCancelForm(resetForm: () => void) {
isFormEntryDialogOpen.value = false
selectedInstallationId.value = ''
selectedUnitId.value = ''
setTimeout(() => {
resetForm()
}, 500)
}
function onInstallationChanged(installationId: string) {
selectedInstallationId.value = installationId
}
function onUnitChanged(unitId: string) {
selectedUnitId.value = unitId
}
async function onSubmitForm(values: any, resetForm: () => void) {
let isSuccess = false
try {
// TODO: Implement form submission logic
console.log('Form submitted:', values)
// Simulate API call
// const response = await xfetch('/api/v1/subspecialist', {
// method: 'POST',
// body: JSON.stringify(values)
// })
// If successful, mark as success and close dialog
isFormEntryDialogOpen.value = false
isSuccess = true
// Reset selection ketika form berhasil submit
selectedInstallationId.value = ''
selectedUnitId.value = ''
// Refresh data after successful submission
await getSubSpecialistList()
// TODO: Show success message
console.log('Sub Specialist created successfully')
} catch (error: unknown) {
console.warn('Error submitting form:', error)
isSuccess = false
// Don't close dialog or reset form on error
// TODO: Show error message to user
} finally {
if (isSuccess) {
setTimeout(() => {
resetForm()
}, 500)
}
}
}
// #endregion
// #region Watchers
// Watch for row actions
watch(recId, () => {
switch (recAction.value) {
case ActionEvents.showEdit:
// TODO: Handle edit action
// isFormEntryDialogOpen.value = true
break
case ActionEvents.showConfirmDelete:
// Trigger confirmation modal open
isRecordConfirmationOpen.value = true
break
}
})
// #endregion
</script>
<template>
<Header v-model="searchInput" :prep="headerPrep" @search="handleSearch" />
<AppSubspecialistList :data="data" :pagination-meta="paginationMeta" @page-change="handlePageChange" />
<Dialog v-model:open="isFormEntryDialogOpen" :title="headerPrep.addNav?.label!" size="lg" prevent-outside>
<AppSubspecialistEntryFormPrev
:installation="installationConf"
:unit="filteredUnitConf"
:specialist="specialistConf"
:schema="schemaConf"
:initial-values="{ name: '', code: '', installationId: '', unitId: '', specialistId: '' }"
@submit="onSubmitForm"
@cancel="onCancelForm"
@installation-changed="onInstallationChanged"
@unit-changed="onUnitChanged"
/>
</Dialog>
<RecordConfirmation
v-model:open="isRecordConfirmationOpen"
action="delete"
:record="recItem"
@confirm="handleConfirmDelete"
@cancel="handleCancelConfirmation"
>
<template #default="{ record }">
<div class="text-sm">
<p><strong>ID:</strong> {{ record?.id }}</p>
<p v-if="record?.name"><strong>Nama:</strong> {{ record.name }}</p>
<p v-if="record?.code"><strong>Kode:</strong> {{ record.code }}</p>
<p v-if="record?.specialist"><strong>Specialist:</strong> {{ record.specialist?.name }}</p>
</div>
</template>
</RecordConfirmation>
</template>
@@ -28,11 +28,12 @@ import {
handleActionRemove,
handleCancelForm,
} from '~/handlers/subspecialist.handler'
import { specialists, getSpecialistsList } from '~/handlers/_shared.handler'
// Services
import { getSubspecialists, getSubspecialistDetail } from '~/services/subspecialist.service'
import { getList, getDetail } from '~/services/subspecialist.service'
import { getValueLabelList as getSpecialistsList } from '~/services/specialist.service'
const specialists = ref<{ value: string | number; label: string }[]>([])
const title = ref('')
const {
@@ -45,10 +46,10 @@ const {
fetchData: getSubSpecialistList,
} = usePaginatedList({
fetchFn: async (params: any) => {
// TODO: use pagination params
const result = await getSubspecialists({
const result = await getList({
search: params.search,
page: params['page-number'] || 0,
'page-number': params['page-number'] || 0,
'page-size': params['page-size'] || 10,
includes: 'specialist',
})
return { success: result.success || false, body: result.body || {} }
@@ -88,7 +89,7 @@ provide('rec_item', recItem)
provide('table_data_loader', isLoading)
const getCurrentSubSpecialistDetail = async (id: number | string) => {
const result = await getSubspecialistDetail(id)
const result = await getDetail(id)
if (result.success) {
const currentValue = result.body?.data || {}
recItem.value = currentValue
@@ -116,8 +117,8 @@ watch([recId, recAction], () => {
})
onMounted(async () => {
specialists.value = await getSpecialistsList()
await getSubSpecialistList()
await getSpecialistsList()
})
</script>
+3 -2
View File
@@ -28,11 +28,12 @@ import {
handleActionRemove,
handleCancelForm,
} from '~/handlers/device.handler'
import { uoms, getUomList } from '~/handlers/_shared.handler'
// Services
import { getList, getDetail } from '~/services/device.service'
import { getValueLabelList as getUomList } from '~/services/uom.service'
const uoms = ref<{ value: string; label: string }[]>([])
const title = ref('')
const {
@@ -116,8 +117,8 @@ watch([recId, recAction], () => {
})
onMounted(async () => {
uoms.value = await getUomList()
await getToolsList()
await getUomList()
})
</script>
-207
View File
@@ -1,207 +0,0 @@
<script setup lang="ts">
import type { HeaderPrep } from '~/components/pub/my-ui/data/types'
import AppUnitEntryForm from '~/components/app/unit/entry-form.vue'
import Dialog from '~/components/pub/my-ui/modal/dialog.vue'
import RecordConfirmation from '~/components/pub/my-ui/confirmation/record-confirmation.vue'
import { ActionEvents } from '~/components/pub/my-ui/data/types'
import Header from '~/components/pub/my-ui/nav-header/header.vue'
import { usePaginatedList } from '~/composables/usePaginatedList'
import { schemaConf, unitConf } from './entry'
// #region State & Computed
// Dialog state
const isFormEntryDialogOpen = ref(false)
const isRecordConfirmationOpen = ref(false)
// Table action rowId provider
const recId = ref<number>(0)
const recAction = ref<string>('')
const recItem = ref<any>(null)
async function fetchUnitData(params: any) {
const endpoint = transform('/api/v1/patient', params)
return await xfetch(endpoint)
}
// Menggunakan composable untuk pagination
const {
data,
isLoading,
paginationMeta,
searchInput,
handlePageChange,
handleSearch,
fetchData: getUnitList,
} = usePaginatedList({
fetchFn: fetchUnitData,
entityName: 'unit',
})
const headerPrep: HeaderPrep = {
title: 'Unit',
icon: 'i-lucide-box',
refSearchNav: {
placeholder: 'Cari (min. 3 karakter)...',
minLength: 3,
debounceMs: 500,
showValidationFeedback: true,
onInput: (_val: string) => {
// Handle search input - this will be triggered by the header component
},
onClick: () => {
// Handle search button click if needed
},
onClear: () => {
// Handle search clear
},
},
addNav: {
label: 'Tambah Unit',
icon: 'i-lucide-send',
onClick: () => {
isFormEntryDialogOpen.value = true
},
},
}
provide('rec_id', recId)
provide('rec_action', recAction)
provide('rec_item', recItem)
provide('table_data_loader', isLoading)
// #endregion
// #region Functions
async function handleDeleteRow(record: any) {
try {
// TODO : hit backend request untuk delete
console.log('Deleting record:', record)
// Simulate API call
// const response = await xfetch(`/api/v1/unit/${record.id}`, {
// method: 'DELETE'
// })
// Refresh data setelah berhasil delete
await getUnitList()
// TODO: Show success message
console.log('Record deleted successfully')
} catch (error) {
console.error('Error deleting record:', error)
// TODO: Show error message
} finally {
// Reset record state
recId.value = 0
recAction.value = ''
recItem.value = null
}
}
// #endregion region
// #region Form event handlers
function onCancelForm(resetForm: () => void) {
isFormEntryDialogOpen.value = false
setTimeout(() => {
resetForm()
}, 500)
}
async function onSubmitForm(values: any, resetForm: () => void) {
let isSuccess = false
try {
// TODO: Implement form submission logic
console.log('Form submitted:', values)
// Simulate API call
// const response = await xfetch('/api/v1/unit', {
// method: 'POST',
// body: JSON.stringify(values)
// })
// If successful, mark as success and close dialog
isFormEntryDialogOpen.value = false
isSuccess = true
// Refresh data after successful submission
await getUnitList()
// TODO: Show success message
console.log('Unit created successfully')
} catch (error: unknown) {
console.warn('Error submitting form:', error)
isSuccess = false
// Don't close dialog or reset form on error
// TODO: Show error message to user
} finally {
if (isSuccess) {
setTimeout(() => {
resetForm()
}, 500)
}
}
}
// #endregion
// #region Watchers
// Watch for row actions
watch(recId, () => {
switch (recAction.value) {
case ActionEvents.showEdit:
// TODO: Handle edit action
// isFormEntryDialogOpen.value = true
break
case ActionEvents.showConfirmDelete:
// Trigger confirmation modal open
isRecordConfirmationOpen.value = true
break
}
})
// Handle confirmation result
function handleConfirmDelete(record: any, action: string) {
console.log('Confirmed action:', action, 'for record:', record)
handleDeleteRow(record)
}
function handleCancelConfirmation() {
// Reset record state when cancelled
recId.value = 0
recAction.value = ''
recItem.value = null
}
// #endregion
</script>
<template>
<Header v-model="searchInput" :prep="headerPrep" @search="handleSearch" />
<AppUnitList :data="data" :pagination-meta="paginationMeta" @page-change="handlePageChange" />
<Dialog v-model:open="isFormEntryDialogOpen" title="Tambah Unit" size="lg" prevent-outside>
<AppUnitEntryFormPrev
:unit="unitConf" :schema="schemaConf" :initial-values="{ name: '', code: '', parentId: '' }"
@submit="onSubmitForm" @cancel="onCancelForm"
/>
</Dialog>
<!-- Record Confirmation Modal -->
<RecordConfirmation
v-model:open="isRecordConfirmationOpen" action="delete" :record="recItem"
@confirm="handleConfirmDelete" @cancel="handleCancelConfirmation"
>
<template #default="{ record }">
<div class="text-sm">
<p><strong>ID:</strong> {{ record?.id }}</p>
<p v-if="record?.firstName"><strong>Nama:</strong> {{ record.firstName }}</p>
<p v-if="record?.code"><strong>Kode:</strong> {{ record.cellphone }}</p>
</div>
</template>
</RecordConfirmation>
</template>
<style scoped>
/* component style */
</style>
+8 -7
View File
@@ -28,11 +28,12 @@ import {
handleActionRemove,
handleCancelForm,
} from '~/handlers/unit.handler'
import { installations, getInstallationList } from '~/handlers/_shared.handler'
// Services
import { getUnits, getUnitDetail } from '~/services/unit.service'
import { getList, getDetail } from '~/services/unit.service'
import { getValueLabelList as getInstallationList } from '~/services/installation.service'
const installations = ref<{ value: string; label: string }[]>([])
const title = ref('')
const {
@@ -45,10 +46,10 @@ const {
fetchData: getUnitList,
} = usePaginatedList({
fetchFn: async (params: any) => {
// TODO: use pagination params
const result = await getUnits({
const result = await getList({
search: params.search,
page: params['page-number'] || 0,
'page-number': params['page-number'] || 0,
'page-size': params['page-size'] || 10,
includes: 'installation',
})
return { success: result.success || false, body: result.body || {} }
@@ -88,7 +89,7 @@ provide('rec_item', recItem)
provide('table_data_loader', isLoading)
const getCurrentUnitDetail = async (id: number | string) => {
const result = await getUnitDetail(id)
const result = await getDetail(id)
if (result.success) {
const currentValue = result.body?.data || {}
recItem.value = currentValue
@@ -116,8 +117,8 @@ watch([recId, recAction], () => {
})
onMounted(async () => {
installations.value = await getInstallationList()
await getUnitList()
await getInstallationList()
})
</script>
+7 -3
View File
@@ -29,7 +29,7 @@ import {
} from '~/handlers/uom.handler'
// Services
import { getUoms, getUomDetail } from '~/services/uom.service'
import { getList, getDetail } from '~/services/uom.service'
const title = ref('')
@@ -43,7 +43,11 @@ const {
fetchData: getUomList,
} = usePaginatedList({
fetchFn: async (params: any) => {
const result = await getUoms({ search: params.search, page: params['page-number'] || 0 })
const result = await getList({
search: params.search,
'page-number': params['page-number'] || 0,
'page-size': params['page-size'] || 10,
})
return { success: result.success || false, body: result.body || {} }
},
entityName: 'uom',
@@ -81,7 +85,7 @@ provide('rec_item', recItem)
provide('table_data_loader', isLoading)
const getCurrentUomDetail = async (id: number | string) => {
const result = await getUomDetail(id)
const result = await getDetail(id)
if (result.success) {
const currentValue = result.body?.data || {}
recItem.value = currentValue
-5
View File
@@ -1,5 +0,0 @@
// variables
export const installations = ref<{ value: string; label: string }[]>([])
export const specialists = ref<{ value: string | number; label: string }[]>([])
export const units = ref<{ value: string | number; label: string }[]>([])