Files
antrean-operasi/components/pendaftaran/ModalPendaftaran.vue
T
2026-02-23 10:09:13 +07:00

361 lines
13 KiB
Vue

<script setup lang="ts">
import BiodataPasien from '@/components/pendaftaran/BiodataPasien.vue';
import MedisPasien from '@/components/pendaftaran/MedisPasien.vue';
import RencanaOperasi from '@/components/pendaftaran/RencanaOperasi.vue';
import StatusPasienOperasi from '@/components/pendaftaran/StatusPasienOperasi.vue';
import LoadingState from '@/components/shared/LoadingState.vue';
import { usePendaftaranStore } from '@/store/pendaftaran';
import { getAntrianOperasiById } from '@/services/antrean';
import { storeToRefs } from 'pinia';
import type { PageMode } from '~/types/common';
interface Props {
modelValue: boolean;
mode?: PageMode;
id?: string | number;
initialKategori?: number;
initialSpesialis?: number;
initialSubSpesialis?: number;
}
const props = withDefaults(defineProps<Props>(), {
mode: 'create',
id: undefined,
initialKategori: undefined,
initialSpesialis: undefined,
initialSubSpesialis: undefined
});
const emit = defineEmits<{
'update:modelValue': [value: boolean];
'success': [];
}>();
const pendaftaranStore = usePendaftaranStore();
// Use storeToRefs to make state reactive
const {
formData,
diagnosisItems,
tindakanItems,
rencanaOperasiData,
dokterPelaksanaItems,
statusPasienData,
loading: formLoading
} = storeToRefs(pendaftaranStore);
const form = ref();
const valid = ref(true);
const loadingDetail = ref(false);
const errorDetail = ref(false);
// Refs for child components
const biodataPasienRef = ref();
const medisPasienRef = ref();
const rencanaOperasiRef = ref();
// Computed properties
const isReadonly = computed(() => props.mode === 'view');
const modalTitle = computed(() => {
switch (props.mode) {
case 'edit':
return 'Edit Pendaftaran Antrean Operasi';
case 'view':
return 'Detail Pendaftaran Antrean Operasi';
default:
return 'Tambah Pendaftaran Antrean Operasi';
}
});
const showModal = computed({
get: () => props.modelValue,
set: (value) => emit('update:modelValue', value)
});
// Initialize form when modal opens
watch(() => props.modelValue, async (isOpen) => {
if (isOpen) {
await initializeForm();
}
}, { immediate: true }); // Add immediate to handle initial render
// Fetch detail data for edit mode
const fetchDetailData = async () => {
if (!props.id) return;
loadingDetail.value = true;
errorDetail.value = false;
try {
const response = await getAntrianOperasiById(props.id);
if (response.success && response.data) {
const data = response.data;
// Set form data to store
if (data.formData) {
pendaftaranStore.formData = { ...data.formData };
}
// Set diagnosis items to store
if (data.diagnosisItems) {
pendaftaranStore.diagnosisItems = [...data.diagnosisItems];
}
// Set tindakan items to store
if (data.tindakanItems) {
pendaftaranStore.tindakanItems = [...data.tindakanItems];
}
// Set rencana operasi data to store
if (data.rencanaOperasiData) {
const rencanaData = { ...data.rencanaOperasiData };
// Convert tanggalDaftar from ISO format to datetime-local format
if (rencanaData.tanggalDaftar) {
// Remove 'Z' and convert to local datetime format
const date = new Date(rencanaData.tanggalDaftar);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
rencanaData.tanggalDaftar = `${year}-${month}-${day}T${hours}:${minutes}`;
}
pendaftaranStore.rencanaOperasiData = rencanaData;
}
// Set dokter pelaksana items to store with proper mapping
if (data.dokterPelaksanaItems && Array.isArray(data.dokterPelaksanaItems)) {
// Map the API response to match Dokter interface
pendaftaranStore.dokterPelaksanaItems = data.dokterPelaksanaItems.map((dokter: any) => ({
id: dokter.id,
nip: dokter.nip,
nama_lengkap: dokter.nama || dokter.nama_lengkap,
hfis_code: dokter.hfis_code || null,
nama_ksm: dokter.satuan_kerja || dokter.nama_ksm || ''
}));
}
// Set status pasien data to store
if (data.statusPasienData) {
pendaftaranStore.statusPasienData = { ...data.statusPasienData };
}
}
} catch (err) {
console.error('Error fetching detail data:', err);
errorDetail.value = true;
pendaftaranStore.showSnackbar('Gagal mengambil data detail', 'error');
} finally {
loadingDetail.value = false;
}
};
const initializeForm = async () => {
// If edit or view mode, load data by id first
if ((props.mode === 'edit' || props.mode === 'view') && props.id) {
await fetchDetailData();
return; // Don't reset form for edit/view mode
}
// For create mode, reset form and set defaults
pendaftaranStore.resetForm();
// Set default tanggal daftar ke now
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
pendaftaranStore.rencanaOperasiData.tanggalDaftar = `${year}-${month}-${day}T${hours}:${minutes}`;
// Set initial values from parent context
if (props.initialKategori !== undefined) {
pendaftaranStore.rencanaOperasiData.kategoriOperasi = props.initialKategori;
}
if (props.initialSpesialis !== undefined) {
pendaftaranStore.rencanaOperasiData.spesialis = props.initialSpesialis;
}
if (props.initialSubSpesialis !== undefined) {
pendaftaranStore.rencanaOperasiData.subSpesialis = props.initialSubSpesialis;
}
};
const closeModal = () => {
showModal.value = false;
};
// Focus on error field - scroll to component and focus on first invalid field
const focusOnErrorField = (fieldName: string) => {
nextTick(() => {
// Map field names to component refs and actions
const fieldMap: Record<string, { ref: any; action: string }> = {
'noRekamMedis': { ref: biodataPasienRef, action: 'focusNoRekamMedis' },
'diagnosis': { ref: medisPasienRef, action: 'focusDiagnosis' },
'tindakan': { ref: medisPasienRef, action: 'focusTindakan' },
'spesialis': { ref: rencanaOperasiRef, action: 'focusSpesialis' },
'subSpesialis': { ref: rencanaOperasiRef, action: 'focusSubSpesialis' },
'tanggalDaftar': { ref: rencanaOperasiRef, action: 'focusTanggalDaftar' },
'kategoriOperasi': { ref: rencanaOperasiRef, action: 'focusKategoriOperasi' },
};
const fieldInfo = fieldMap[fieldName];
if (fieldInfo && fieldInfo.ref.value) {
// Scroll to component
const componentElement = fieldInfo.ref.value.$el;
if (componentElement) {
componentElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
// Call focus method if available
setTimeout(() => {
if (typeof fieldInfo.ref.value[fieldInfo.action] === 'function') {
fieldInfo.ref.value[fieldInfo.action]();
}
}, 300); // Wait for scroll to complete
}
}
});
};
const handleSimpan = async () => {
const { valid: isValid } = await form.value.validate();
if (isValid) {
// Validate using store method
const validation = pendaftaranStore.validateForm();
if (!validation.valid) {
const errorMessage = `Data berikut harus diisi:\n\n${validation.errors.join(', ')}`;
pendaftaranStore.showSnackbar(errorMessage, 'error');
// Focus on first error field
if (validation.firstErrorField) {
focusOnErrorField(validation.firstErrorField);
}
return;
}
// Submit form using store action - pass id if edit mode
const result = props.mode === 'edit' && props.id
? await pendaftaranStore.submitForm(props.id)
: await pendaftaranStore.submitForm();
if (result.success) {
emit('success');
closeModal();
}
} else {
// Validate using store method
const validation = pendaftaranStore.validateForm();
const errorMessage = `Data berikut harus diisi:\n\n${validation.errors.join(', ')}`;
pendaftaranStore.showSnackbar(errorMessage, 'error');
// Focus on first error field
if (validation.firstErrorField) {
focusOnErrorField(validation.firstErrorField);
}
}
};
const handleReset = () => {
if (confirm('Apakah Anda yakin ingin mereset form?')) {
form.value.reset();
pendaftaranStore.resetForm();
// Set default tanggal daftar ke now
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
pendaftaranStore.rencanaOperasiData.tanggalDaftar = `${year}-${month}-${day}T${hours}:${minutes}`;
}
};
</script>
<template>
<v-dialog v-model="showModal" max-width="900px" persistent scrollable>
<v-card>
<v-card-title class="d-flex align-center justify-space-between pa-3">
<div class="d-flex align-center">
<v-icon class="mr-3 text-primary">mdi-clipboard-plus</v-icon>
<span class="text-h6">{{ modalTitle }}</span>
</div>
<v-btn
icon
variant="text"
@click="closeModal"
>
<v-icon>mdi-close</v-icon>
</v-btn>
</v-card-title>
<v-card-text class="bg-background pa-5" style="max-height: 70vh; overflow-y: auto; overflow-x: hidden;">
<LoadingState
:loading="loadingDetail"
:error="errorDetail"
loading-text="Memuat data pendaftaran..."
error-text="Gagal memuat data pendaftaran"
@retry="fetchDetailData"
>
<v-form ref="form" v-model="valid" lazy-validation>
<v-row no-gutters>
<!-- Data Biodata Pasien -->
<v-col cols="12" class="mb-5">
<BiodataPasien ref="biodataPasienRef" v-model="formData" :readonly="isReadonly" />
</v-col>
<!-- Data Medis Pasien -->
<v-col cols="12" class="mb-5">
<MedisPasien
ref="medisPasienRef"
v-model:diagnosis-items="diagnosisItems"
v-model:tindakan-items="tindakanItems"
:readonly="isReadonly"
/>
</v-col>
<!-- Data Rencana Operasi -->
<v-col cols="12">
<RencanaOperasi
ref="rencanaOperasiRef"
v-model:rencana-operasi-data="rencanaOperasiData"
v-model:dokter-pelaksana-items="dokterPelaksanaItems"
:readonly="isReadonly"
/>
</v-col>
</v-row>
</v-form>
</LoadingState>
</v-card-text>
<v-card-actions class="pa-4 justify-end">
<v-btn
v-if="!isReadonly"
color="error"
size="large"
variant="outlined"
prepend-icon="mdi-reload"
@click="handleReset"
>
Reset
</v-btn>
<v-btn
v-if="!isReadonly"
color="primary"
size="large"
:loading="formLoading"
@click="handleSimpan"
variant="flat"
prepend-icon="mdi-content-save"
>
Simpan
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>