361 lines
13 KiB
Vue
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>
|