feat(specialist): add installation-unit relationship to specialist form
- Implement installation-unit mapping and filtering logic - Update entry form to handle installation selection and unit filtering - Add installation and unit dropdowns with combobox components
This commit is contained in:
No files matched your search
@@ -1,26 +1,52 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { FormErrors } from '~/types/error'
|
import type { FormErrors } from '~/types/error'
|
||||||
import { toTypedSchema } from '@vee-validate/zod'
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
|
import Combobox from '~/components/pub/custom-ui/form/combobox.vue'
|
||||||
import FieldGroup from '~/components/pub/custom-ui/form/field-group.vue'
|
import FieldGroup from '~/components/pub/custom-ui/form/field-group.vue'
|
||||||
import Field from '~/components/pub/custom-ui/form/field.vue'
|
import Field from '~/components/pub/custom-ui/form/field.vue'
|
||||||
import Label from '~/components/pub/custom-ui/form/label.vue'
|
import Label from '~/components/pub/custom-ui/form/label.vue'
|
||||||
import Select from '~/components/pub/custom-ui/form/select.vue'
|
|
||||||
|
|
||||||
interface SpecialistFormData {
|
interface SpecialistFormData {
|
||||||
name: string
|
name: string
|
||||||
code: string
|
code: string
|
||||||
encounterClassCode: string
|
installationId: string
|
||||||
|
unitId: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
schema: any
|
schema: any
|
||||||
initialValues?: Partial<SpecialistFormData>
|
initialValues?: Partial<SpecialistFormData>
|
||||||
errors?: FormErrors
|
errors?: FormErrors
|
||||||
|
installation: {
|
||||||
|
msg: {
|
||||||
|
placeholder: string
|
||||||
|
search: string
|
||||||
|
empty: string
|
||||||
|
}
|
||||||
|
items: {
|
||||||
|
value: string
|
||||||
|
label: string
|
||||||
|
code: string
|
||||||
|
}[]
|
||||||
|
}
|
||||||
|
unit: {
|
||||||
|
msg: {
|
||||||
|
placeholder: string
|
||||||
|
search: string
|
||||||
|
empty: string
|
||||||
|
}
|
||||||
|
items: {
|
||||||
|
value: string
|
||||||
|
label: string
|
||||||
|
code: string
|
||||||
|
}[]
|
||||||
|
}
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
'submit': [values: SpecialistFormData, resetForm: () => void]
|
'submit': [values: SpecialistFormData, resetForm: () => void]
|
||||||
'cancel': [resetForm: () => void]
|
'cancel': [resetForm: () => void]
|
||||||
|
'installationChanged': [id: string]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const formSchema = toTypedSchema(props.schema)
|
const formSchema = toTypedSchema(props.schema)
|
||||||
@@ -30,7 +56,8 @@ function onSubmitForm(values: any, { resetForm }: { resetForm: () => void }) {
|
|||||||
const formData: SpecialistFormData = {
|
const formData: SpecialistFormData = {
|
||||||
name: values.name || '',
|
name: values.name || '',
|
||||||
code: values.code || '',
|
code: values.code || '',
|
||||||
encounterClassCode: values.encounterClassCode || '',
|
installationId: values.installationId || '',
|
||||||
|
unitId: values.unitId || '',
|
||||||
}
|
}
|
||||||
emit('submit', formData, resetForm)
|
emit('submit', formData, resetForm)
|
||||||
}
|
}
|
||||||
@@ -39,11 +66,17 @@ function onSubmitForm(values: any, { resetForm }: { resetForm: () => void }) {
|
|||||||
function onCancelForm({ resetForm }: { resetForm: () => void }) {
|
function onCancelForm({ resetForm }: { resetForm: () => void }) {
|
||||||
emit('cancel', resetForm)
|
emit('cancel', resetForm)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Watch for installation changes
|
||||||
|
function onInstallationChanged(installationId: string, setFieldValue: any) {
|
||||||
|
setFieldValue('unitId', '', false)
|
||||||
|
emit('installationChanged', installationId || '')
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Form
|
<Form
|
||||||
v-slot="{ handleSubmit, resetForm }" as="" keep-values :validation-schema="formSchema"
|
v-slot="{ handleSubmit, resetForm, values, setFieldValue }" as="" keep-values :validation-schema="formSchema"
|
||||||
:initial-values="initialValues"
|
:initial-values="initialValues"
|
||||||
>
|
>
|
||||||
<form id="entry-form" @submit="handleSubmit($event, (values) => onSubmitForm(values, { resetForm }))">
|
<form id="entry-form" @submit="handleSubmit($event, (values) => onSubmitForm(values, { resetForm }))">
|
||||||
@@ -56,9 +89,12 @@ function onCancelForm({ resetForm }: { resetForm: () => void }) {
|
|||||||
<FormItem>
|
<FormItem>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
id="name" type="text" placeholder="Masukkan nama instalasi" autocomplete="off"
|
id="name"
|
||||||
|
type="text"
|
||||||
|
placeholder="Masukkan nama spesialisasi"
|
||||||
|
autocomplete="off"
|
||||||
v-bind="componentField"
|
v-bind="componentField"
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@@ -72,7 +108,57 @@ function onCancelForm({ resetForm }: { resetForm: () => void }) {
|
|||||||
<FormField v-slot="{ componentField }" name="code">
|
<FormField v-slot="{ componentField }" name="code">
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input id="code" type="text" placeholder="Masukkan kode instalasi" autocomplete="off" v-bind="componentField" />
|
<Input
|
||||||
|
id="code"
|
||||||
|
type="text"
|
||||||
|
placeholder="Masukkan kode spesialisasi"
|
||||||
|
autocomplete="off"
|
||||||
|
v-bind="componentField"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</Field>
|
||||||
|
</FieldGroup>
|
||||||
|
|
||||||
|
<FieldGroup>
|
||||||
|
<Label label-for="installationId">Instalasi</Label>
|
||||||
|
<Field id="installationId" :errors="errors">
|
||||||
|
<FormField v-slot="{ componentField }" name="installationId">
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<Combobox
|
||||||
|
id="installationId"
|
||||||
|
v-bind="componentField"
|
||||||
|
:items="installation.items"
|
||||||
|
:placeholder="installation.msg.placeholder"
|
||||||
|
:search-placeholder="installation.msg.search"
|
||||||
|
:empty-message="installation.msg.empty"
|
||||||
|
@update:model-value="(value) => onInstallationChanged(value, setFieldValue)"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
</Field>
|
||||||
|
</FieldGroup>
|
||||||
|
|
||||||
|
<FieldGroup>
|
||||||
|
<Label label-for="unitId">Unit</Label>
|
||||||
|
<Field id="unitId" :errors="errors">
|
||||||
|
<FormField v-slot="{ componentField }" name="unitId">
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<Combobox
|
||||||
|
id="unitId"
|
||||||
|
:disabled="!values.installationId"
|
||||||
|
v-bind="componentField"
|
||||||
|
:items="unit.items"
|
||||||
|
:placeholder="unit.msg.placeholder"
|
||||||
|
:search-placeholder="unit.msg.search"
|
||||||
|
:empty-message="unit.msg.empty"
|
||||||
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as z from 'zod'
|
import * as z from 'zod'
|
||||||
|
|
||||||
export const schemaConf = z.object({
|
export const specialistConf = z.object({
|
||||||
name: z
|
name: z
|
||||||
.string({
|
.string({
|
||||||
required_error: 'Nama spesialisasi harus diisi',
|
required_error: 'Nama spesialisasi harus diisi',
|
||||||
@@ -13,9 +13,83 @@ export const schemaConf = z.object({
|
|||||||
})
|
})
|
||||||
.min(3, 'Kode spesialisasi minimal 3 karakter'),
|
.min(3, 'Kode spesialisasi minimal 3 karakter'),
|
||||||
|
|
||||||
|
installationId: z
|
||||||
|
.string({
|
||||||
|
required_error: 'Instalasi harus dipilih',
|
||||||
|
})
|
||||||
|
.min(1, 'Instalasi harus dipilih'),
|
||||||
|
|
||||||
unitId: z
|
unitId: z
|
||||||
.string({
|
.string({
|
||||||
required_error: 'Unit harus dipilih',
|
required_error: 'Unit harus dipilih',
|
||||||
})
|
})
|
||||||
.min(1, 'Unit harus dipilih'),
|
.min(1, 'Unit harus dipilih'),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Unit mapping berdasarkan installation
|
||||||
|
export const installationUnitMapping: Record<string, string[]> = {
|
||||||
|
'1': ['1', '3', '5'],
|
||||||
|
'2': ['2', '4', '6'],
|
||||||
|
'3': ['7', '8', '9', '10', '11'],
|
||||||
|
}
|
||||||
|
|
||||||
|
export const unitConf = {
|
||||||
|
msg: {
|
||||||
|
placeholder: '---pilih unit',
|
||||||
|
search: 'kode, nama unit',
|
||||||
|
empty: 'unit tidak ditemukan',
|
||||||
|
},
|
||||||
|
items: [
|
||||||
|
{ value: '1', label: 'Instalasi Medis', code: 'MED' },
|
||||||
|
{ value: '2', label: 'Instalasi Keperawatan', code: 'NUR' },
|
||||||
|
{ value: '3', label: 'Instalasi Administrasi', code: 'ADM' },
|
||||||
|
{ value: '4', label: 'Instalasi Penunjang Non-Medis', code: 'SUP' },
|
||||||
|
{ value: '5', label: 'Instalasi Pendidikan & Pelatihan', code: 'EDU' },
|
||||||
|
{ value: '6', label: 'Instalasi Farmasi', code: 'PHA' },
|
||||||
|
{ value: '7', label: 'Instalasi Radiologi', code: 'RAD' },
|
||||||
|
{ value: '8', label: 'Instalasi Laboratorium', code: 'LAB' },
|
||||||
|
{ value: '9', label: 'Instalasi Keuangan', code: 'FIN' },
|
||||||
|
{ value: '10', label: 'Instalasi SDM', code: 'HR' },
|
||||||
|
{ value: '11', label: 'Instalasi Teknologi Informasi', code: 'ITS' },
|
||||||
|
{ value: '12', label: 'Instalasi Pemeliharaan & Sarana', code: 'MNT' },
|
||||||
|
{ value: '13', label: 'Instalasi Gizi / Catering', code: 'CAT' },
|
||||||
|
{ value: '14', label: 'Instalasi Keamanan', code: 'SEC' },
|
||||||
|
{ value: '15', label: 'Instalasi Gawat Darurat', code: 'EMR' },
|
||||||
|
{ value: '16', label: 'Instalasi Bedah Sentral', code: 'SUR' },
|
||||||
|
{ value: '17', label: 'Instalasi Rawat Jalan', code: 'OUT' },
|
||||||
|
{ value: '18', label: 'Instalasi Rawat Inap', code: 'INP' },
|
||||||
|
{ value: '19', label: 'Instalasi Rehabilitasi Medik', code: 'REB' },
|
||||||
|
{ value: '20', label: 'Instalasi Penelitian & Pengembangan', code: 'RSH' },
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
export const installationConf = {
|
||||||
|
msg: {
|
||||||
|
placeholder: '---pilih instalasi',
|
||||||
|
search: 'kode, nama instalasi',
|
||||||
|
empty: 'instalasi tidak ditemukan',
|
||||||
|
},
|
||||||
|
items: [
|
||||||
|
{ value: '1', label: 'Ambulatory', code: 'AMB' },
|
||||||
|
{ value: '2', label: 'Inpatient', code: 'IMP' },
|
||||||
|
{ value: '3', label: 'Emergency', code: 'EMER' },
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function untuk filter unit berdasarkan installation
|
||||||
|
export function getFilteredUnits(installationId: string) {
|
||||||
|
if (!installationId || !installationUnitMapping[installationId]) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const allowedUnitIds = installationUnitMapping[installationId]
|
||||||
|
return unitConf.items.filter((unit) => allowedUnitIds.includes(unit.value))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function untuk membuat unit config yang ter-filter
|
||||||
|
export function createFilteredUnitConf(installationId: string) {
|
||||||
|
return {
|
||||||
|
...unitConf,
|
||||||
|
items: getFilteredUnits(installationId),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ import RecordConfirmation from '~/components/pub/custom-ui/confirmation/record-c
|
|||||||
import { ActionEvents } from '~/components/pub/custom-ui/data/types'
|
import { ActionEvents } from '~/components/pub/custom-ui/data/types'
|
||||||
import Header from '~/components/pub/custom-ui/nav-header/header.vue'
|
import Header from '~/components/pub/custom-ui/nav-header/header.vue'
|
||||||
import { usePaginatedList } from '~/composables/usePaginatedList'
|
import { usePaginatedList } from '~/composables/usePaginatedList'
|
||||||
import { schemaConf } from './entry'
|
import { createFilteredUnitConf, installationConf, specialistConf, unitConf } from './entry'
|
||||||
|
|
||||||
// #region State & Computed
|
// #region State & Computed
|
||||||
// Dialog state
|
// Dialog state
|
||||||
@@ -18,12 +18,17 @@ const recId = ref<number>(0)
|
|||||||
const recAction = ref<string>('')
|
const recAction = ref<string>('')
|
||||||
const recItem = ref<any>(null)
|
const recItem = ref<any>(null)
|
||||||
|
|
||||||
async function fetchSpecialistData(params: any) {
|
// State untuk tracking installation yang dipilih di form
|
||||||
const endpoint = transform('/api/v1/patient', params)
|
const selectedInstallationId = ref<string>('')
|
||||||
return await xfetch(endpoint)
|
|
||||||
}
|
// Computed untuk filtered unit berdasarkan installation yang dipilih
|
||||||
|
const filteredUnitConf = computed(() => {
|
||||||
|
if (!selectedInstallationId.value) {
|
||||||
|
return { ...unitConf, items: [] }
|
||||||
|
}
|
||||||
|
return createFilteredUnitConf(selectedInstallationId.value)
|
||||||
|
})
|
||||||
|
|
||||||
// Menggunakan composable untuk pagination
|
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
isLoading,
|
isLoading,
|
||||||
@@ -71,6 +76,10 @@ provide('table_data_loader', isLoading)
|
|||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
// #region Functions
|
// #region Functions
|
||||||
|
async function fetchSpecialistData(params: any) {
|
||||||
|
const endpoint = transform('/api/v1/patient', params)
|
||||||
|
return await xfetch(endpoint)
|
||||||
|
}
|
||||||
|
|
||||||
async function handleDeleteRow(record: any) {
|
async function handleDeleteRow(record: any) {
|
||||||
try {
|
try {
|
||||||
@@ -98,17 +107,40 @@ async function handleDeleteRow(record: any) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
// #endregion region
|
||||||
|
|
||||||
// #region Form event handlers
|
// #region Form event handlers
|
||||||
|
|
||||||
function onCancelForm(resetForm: () => void) {
|
function onCancelForm(resetForm: () => void) {
|
||||||
isFormEntryDialogOpen.value = false
|
isFormEntryDialogOpen.value = false
|
||||||
|
// Reset installation selection ketika form dibatal
|
||||||
|
selectedInstallationId.value = ''
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
resetForm()
|
resetForm()
|
||||||
}, 500)
|
}, 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) {
|
async function onSubmitForm(values: any, resetForm: () => void) {
|
||||||
let isSuccess = false
|
let isSuccess = false
|
||||||
try {
|
try {
|
||||||
@@ -125,6 +157,9 @@ async function onSubmitForm(values: any, resetForm: () => void) {
|
|||||||
isFormEntryDialogOpen.value = false
|
isFormEntryDialogOpen.value = false
|
||||||
isSuccess = true
|
isSuccess = true
|
||||||
|
|
||||||
|
// Reset installation selection ketika form berhasil submit
|
||||||
|
selectedInstallationId.value = ''
|
||||||
|
|
||||||
// Refresh data after successful submission
|
// Refresh data after successful submission
|
||||||
await getSpecialistList()
|
await getSpecialistList()
|
||||||
|
|
||||||
@@ -161,18 +196,8 @@ watch(recId, () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Handle confirmation result
|
// Note: Installation change logic is now handled by the entry-form component
|
||||||
function handleConfirmDelete(record: any, action: string) {
|
// through the onInstallationChanged event handler
|
||||||
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
|
// #endregion
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -182,12 +207,25 @@ function handleCancelConfirmation() {
|
|||||||
<AppSpecialistList :data="data" :pagination-meta="paginationMeta" @page-change="handlePageChange" />
|
<AppSpecialistList :data="data" :pagination-meta="paginationMeta" @page-change="handlePageChange" />
|
||||||
|
|
||||||
<Dialog v-model:open="isFormEntryDialogOpen" :title="headerPrep.addNav?.label!" size="lg" prevent-outside>
|
<Dialog v-model:open="isFormEntryDialogOpen" :title="headerPrep.addNav?.label!" size="lg" prevent-outside>
|
||||||
<AppSpecialistEntryForm :schema="schemaConf" :initial-values="{ name: '', code: '', encounterClassCode: '' }"
|
<AppSpecialistEntryForm
|
||||||
@submit="onSubmitForm" @cancel="onCancelForm" />
|
:installation="installationConf"
|
||||||
|
:unit="filteredUnitConf"
|
||||||
|
:schema="specialistConf"
|
||||||
|
:disabled-unit="!selectedInstallationId"
|
||||||
|
:initial-values="{ name: '', code: '', installationId: '', unitId: '' }"
|
||||||
|
@submit="onSubmitForm"
|
||||||
|
@cancel="onCancelForm"
|
||||||
|
@installation-changed="onInstallationChanged"
|
||||||
|
/>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
<RecordConfirmation v-model:open="isRecordConfirmationOpen" action="delete" :record="recItem"
|
<RecordConfirmation
|
||||||
@confirm="handleConfirmDelete" @cancel="handleCancelConfirmation">
|
v-model:open="isRecordConfirmationOpen"
|
||||||
|
action="delete"
|
||||||
|
:record="recItem"
|
||||||
|
@confirm="handleConfirmDelete"
|
||||||
|
@cancel="handleCancelConfirmation"
|
||||||
|
>
|
||||||
<template #default="{ record }">
|
<template #default="{ record }">
|
||||||
<div class="text-sm">
|
<div class="text-sm">
|
||||||
<p><strong>ID:</strong> {{ record?.id }}</p>
|
<p><strong>ID:</strong> {{ record?.id }}</p>
|
||||||
|
|||||||
Reference in New Issue
Block a user