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:
@@ -1,26 +1,52 @@
|
||||
<script setup lang="ts">
|
||||
import type { FormErrors } from '~/types/error'
|
||||
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 Field from '~/components/pub/custom-ui/form/field.vue'
|
||||
import Label from '~/components/pub/custom-ui/form/label.vue'
|
||||
import Select from '~/components/pub/custom-ui/form/select.vue'
|
||||
|
||||
interface SpecialistFormData {
|
||||
name: string
|
||||
code: string
|
||||
encounterClassCode: string
|
||||
installationId: string
|
||||
unitId: string
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
schema: any
|
||||
initialValues?: Partial<SpecialistFormData>
|
||||
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<{
|
||||
'submit': [values: SpecialistFormData, resetForm: () => void]
|
||||
'cancel': [resetForm: () => void]
|
||||
'installationChanged': [id: string]
|
||||
}>()
|
||||
|
||||
const formSchema = toTypedSchema(props.schema)
|
||||
@@ -30,7 +56,8 @@ function onSubmitForm(values: any, { resetForm }: { resetForm: () => void }) {
|
||||
const formData: SpecialistFormData = {
|
||||
name: values.name || '',
|
||||
code: values.code || '',
|
||||
encounterClassCode: values.encounterClassCode || '',
|
||||
installationId: values.installationId || '',
|
||||
unitId: values.unitId || '',
|
||||
}
|
||||
emit('submit', formData, resetForm)
|
||||
}
|
||||
@@ -39,11 +66,17 @@ function onSubmitForm(values: any, { resetForm }: { resetForm: () => void }) {
|
||||
function onCancelForm({ resetForm }: { resetForm: () => void }) {
|
||||
emit('cancel', resetForm)
|
||||
}
|
||||
|
||||
// Watch for installation changes
|
||||
function onInstallationChanged(installationId: string, setFieldValue: any) {
|
||||
setFieldValue('unitId', '', false)
|
||||
emit('installationChanged', installationId || '')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<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"
|
||||
>
|
||||
<form id="entry-form" @submit="handleSubmit($event, (values) => onSubmitForm(values, { resetForm }))">
|
||||
@@ -56,9 +89,12 @@ function onCancelForm({ resetForm }: { resetForm: () => void }) {
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
id="name" type="text" placeholder="Masukkan nama instalasi" autocomplete="off"
|
||||
id="name"
|
||||
type="text"
|
||||
placeholder="Masukkan nama spesialisasi"
|
||||
autocomplete="off"
|
||||
v-bind="componentField"
|
||||
/>
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -72,7 +108,57 @@ function onCancelForm({ resetForm }: { resetForm: () => void }) {
|
||||
<FormField v-slot="{ componentField }" name="code">
|
||||
<FormItem>
|
||||
<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>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as z from 'zod'
|
||||
|
||||
export const schemaConf = z.object({
|
||||
export const specialistConf = z.object({
|
||||
name: z
|
||||
.string({
|
||||
required_error: 'Nama spesialisasi harus diisi',
|
||||
@@ -13,9 +13,83 @@ export const schemaConf = z.object({
|
||||
})
|
||||
.min(3, 'Kode spesialisasi minimal 3 karakter'),
|
||||
|
||||
installationId: z
|
||||
.string({
|
||||
required_error: 'Instalasi harus dipilih',
|
||||
})
|
||||
.min(1, 'Instalasi harus dipilih'),
|
||||
|
||||
unitId: z
|
||||
.string({
|
||||
required_error: '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 Header from '~/components/pub/custom-ui/nav-header/header.vue'
|
||||
import { usePaginatedList } from '~/composables/usePaginatedList'
|
||||
import { schemaConf } from './entry'
|
||||
import { createFilteredUnitConf, installationConf, specialistConf, unitConf } from './entry'
|
||||
|
||||
// #region State & Computed
|
||||
// Dialog state
|
||||
@@ -18,12 +18,17 @@ const recId = ref<number>(0)
|
||||
const recAction = ref<string>('')
|
||||
const recItem = ref<any>(null)
|
||||
|
||||
async function fetchSpecialistData(params: any) {
|
||||
const endpoint = transform('/api/v1/patient', params)
|
||||
return await xfetch(endpoint)
|
||||
}
|
||||
// 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)
|
||||
})
|
||||
|
||||
// Menggunakan composable untuk pagination
|
||||
const {
|
||||
data,
|
||||
isLoading,
|
||||
@@ -71,6 +76,10 @@ 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 {
|
||||
@@ -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
|
||||
|
||||
// #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 {
|
||||
@@ -125,6 +157,9 @@ async function onSubmitForm(values: any, resetForm: () => void) {
|
||||
isFormEntryDialogOpen.value = false
|
||||
isSuccess = true
|
||||
|
||||
// Reset installation selection ketika form berhasil submit
|
||||
selectedInstallationId.value = ''
|
||||
|
||||
// Refresh data after successful submission
|
||||
await getSpecialistList()
|
||||
|
||||
@@ -161,18 +196,8 @@ watch(recId, () => {
|
||||
}
|
||||
})
|
||||
|
||||
// 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
|
||||
}
|
||||
// Note: Installation change logic is now handled by the entry-form component
|
||||
// through the onInstallationChanged event handler
|
||||
// #endregion
|
||||
</script>
|
||||
|
||||
@@ -182,12 +207,25 @@ function handleCancelConfirmation() {
|
||||
<AppSpecialistList :data="data" :pagination-meta="paginationMeta" @page-change="handlePageChange" />
|
||||
|
||||
<Dialog v-model:open="isFormEntryDialogOpen" :title="headerPrep.addNav?.label!" size="lg" prevent-outside>
|
||||
<AppSpecialistEntryForm :schema="schemaConf" :initial-values="{ name: '', code: '', encounterClassCode: '' }"
|
||||
@submit="onSubmitForm" @cancel="onCancelForm" />
|
||||
<AppSpecialistEntryForm
|
||||
:installation="installationConf"
|
||||
:unit="filteredUnitConf"
|
||||
:schema="specialistConf"
|
||||
: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">
|
||||
<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>
|
||||
|
||||
Reference in New Issue
Block a user