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

466 lines
21 KiB
Vue

<script setup lang="ts">
import { Icon } from '@iconify/vue';
import type { AntreanOperasi } from '~/types/antrean';
import { STATUS } from '~/types/antrean';
import { getAntrianOperasiById, updateStatusAntrianOperasi } from '@/services/antrean';
interface Props {
modelValue: boolean;
id: string | number;
}
const props = defineProps<Props>();
const emit = defineEmits<{
(e: 'update:modelValue', value: boolean): void;
(e: 'success'): void;
}>();
const dialog = computed({
get: () => props.modelValue,
set: (value) => emit('update:modelValue', value)
});
const loading = ref(false);
const loadingSubmit = ref(false);
const error = ref(false);
// Data pasien - struktur lengkap dari API
const pasienData = ref<any>(null);
// Form data
const formData = ref<{
statusOperasi: string;
tanggalSelesai: string;
keteranganStatus: string;
}>({
statusOperasi: STATUS.BELUM,
tanggalSelesai: '',
keteranganStatus: ''
});
// Snackbar
const snackbar = ref(false);
const snackbarMessage = ref('');
const snackbarColor = ref('success');
const showSnackbar = (message: string, color: string = 'success') => {
snackbarMessage.value = message;
snackbarColor.value = color;
snackbar.value = true;
};
// Fetch data pasien
const fetchData = async () => {
if (!props.id) return;
loading.value = true;
error.value = false;
try {
const response = await getAntrianOperasiById(props.id);
if (response.success && response.data) {
pasienData.value = response.data;
// Set initial form data dari status yang ada
if (pasienData.value.statusPasienData) {
formData.value.statusOperasi = pasienData.value.statusPasienData.statusOperasi || STATUS.BELUM;
formData.value.tanggalSelesai = pasienData.value.statusPasienData.tanggalSelesai || '';
formData.value.keteranganStatus = pasienData.value.statusPasienData.keteranganStatus || '';
} else {
formData.value.statusOperasi = STATUS.BELUM;
formData.value.tanggalSelesai = '';
formData.value.keteranganStatus = '';
}
}
} catch (err) {
console.error('Error fetching data:', err);
error.value = true;
showSnackbar('Gagal mengambil data pasien', 'error');
} finally {
loading.value = false;
}
};
// Validation
const isFormValid = computed(() => {
if (formData.value.statusOperasi === STATUS.SELESAI && !formData.value.tanggalSelesai) {
return false;
}
if ((formData.value.statusOperasi === STATUS.TUNDA || formData.value.statusOperasi === STATUS.BATAL)
&& !formData.value.keteranganStatus.trim()) {
return false;
}
return true;
});
// Submit handler
const handleSubmit = async () => {
if (!isFormValid.value) {
showSnackbar('Mohon lengkapi data yang diperlukan', 'error');
return;
}
loadingSubmit.value = true;
try {
const response = await updateStatusAntrianOperasi(props.id, {
statusOperasi: formData.value.statusOperasi,
tanggalSelesai: formData.value.tanggalSelesai ? new Date(formData.value.tanggalSelesai).toISOString() : null,
keteranganStatus: formData.value.keteranganStatus || null
});
if (response.success) {
showSnackbar('Status antrian berhasil diupdate', 'success');
emit('success');
dialog.value = false;
} else {
showSnackbar( 'Gagal mengupdate status antrian', 'error');
}
} catch (error: any) {
console.error('Error updating status:', error);
showSnackbar('Gagal mengupdate status antrian', 'error');
} finally {
loadingSubmit.value = false;
}
};
// Format date
const formatDate = (date: string) => {
if (!date) return '-';
return new Date(date).toLocaleDateString('id-ID', {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
};
// Helper functions
const getGenderText = (gender: string) => {
return gender === 'L' ? 'Laki-laki' : 'Perempuan';
};
// Min date for tanggal selesai (same as tanggal daftar)
const minTanggalSelesai = computed(() => {
if (!pasienData.value?.rencanaOperasiData?.tanggalDaftar) return '';
return new Date(pasienData.value.rencanaOperasiData.tanggalDaftar).toISOString().slice(0, 16);
});
// Initialize form
const initializeForm = () => {
formData.value = {
statusOperasi: STATUS.BELUM,
tanggalSelesai: '',
keteranganStatus: ''
};
};
// Watch dialog - trigger saat modal dibuka
watch(() => props.modelValue, (isOpen) => {
if (isOpen && props.id) {
initializeForm();
fetchData();
}
}, { immediate: true });
</script>
<template>
<v-dialog v-model="dialog" max-width="900px" persistent scrollable>
<v-card>
<v-card-title class="d-flex align-center justify-space-between pa-4">
<div class="d-flex align-center">
<Icon icon="solar:clipboard-check-bold-duotone" height="28" class="text-primary mr-3" />
<span class="text-h6">Update Status Antrian Operasi</span>
</div>
<v-btn
icon
variant="text"
@click="dialog = false"
:disabled="loadingSubmit"
>
<v-icon>mdi-close</v-icon>
</v-btn>
</v-card-title>
<v-card-text class="pa-5 bg-background" style="max-height: 75vh; overflow-y: auto;">
<!-- Loading State -->
<div v-if="loading" class="text-center py-8">
<v-progress-circular
indeterminate
color="primary"
size="50"
></v-progress-circular>
<p class="mt-4 text-medium-emphasis">Memuat data...</p>
</div>
<!-- Error State -->
<div v-else-if="error" class="text-center py-8">
<v-icon size="64" color="error" class="mb-4">mdi-alert-circle</v-icon>
<p class="text-h6 mb-2">Gagal memuat data</p>
<v-btn color="primary" @click="fetchData" variant="outlined">
<Icon icon="mdi-refresh" height="20" class="mr-2" />
Coba Lagi
</v-btn>
</div>
<!-- Content -->
<div v-else-if="pasienData">
<!-- Informasi Pasien -->
<v-card elevation="2" class="mb-4">
<v-card-title class="bg-blue-lighten-5 pa-4">
<div class="d-flex align-center">
<Icon icon="solar:user-bold-duotone" height="24" class="mr-2 text-primary" />
<span class="text-h6">Informasi Pasien</span>
</div>
</v-card-title>
<v-card-text class="pa-5">
<v-row>
<v-col cols="12" sm="6">
<div class="info-item">
<div class="info-label">
<Icon icon="mdi-card-account-details" height="20" class="mr-2" />
No. Rekam Medis
</div>
<div class="info-value font-weight-bold">
{{ pasienData.formData?.noRekamMedis || '-' }}
</div>
</div>
</v-col>
<v-col cols="12" sm="6">
<div class="info-item">
<div class="info-label">
<Icon icon="solar:user-heart-bold" height="20" class="mr-2" />
Nama Pasien
</div>
<div class="info-value font-weight-bold">
{{ pasienData.formData?.namaPasien || '-' }}
</div>
</div>
</v-col>
<v-col cols="12" sm="6">
<div class="info-item">
<div class="info-label">
<Icon icon="mdi-gender-male-female" height="20" class="mr-2" />
Jenis Kelamin
</div>
<div class="info-value">
{{ getGenderText(pasienData.formData?.jenisKelamin) }}
</div>
</div>
</v-col>
<v-col cols="12" sm="6">
<div class="info-item">
<div class="info-label">
<Icon icon="solar:calendar-date-bold" height="20" class="mr-2" />
Tanggal Daftar
</div>
<div class="info-value">
{{ formatDate(pasienData.rencanaOperasiData?.tanggalDaftar) }}
</div>
</div>
</v-col>
<v-col cols="12" sm="4">
<div class="info-item">
<div class="info-label">
<Icon icon="solar:clipboard-list-bold" height="20" class="mr-2" />
Kategori
</div>
<div class="info-value">
<v-chip size="small" color="primary" variant="flat">
{{ pasienData.rencanaOperasiData?.kategoriName || '-' }}
</v-chip>
</div>
</div>
</v-col>
<v-col cols="12" sm="4">
<div class="info-item">
<div class="info-label">
<Icon icon="solar:stethoscope-bold" height="20" class="mr-2" />
Spesialis
</div>
<div class="info-value">
<v-chip size="small" color="secondary" variant="flat">
{{ pasienData.rencanaOperasiData?.SpesialisName || '-' }}
</v-chip>
</div>
</div>
</v-col>
<v-col cols="12" sm="4">
<div class="info-item">
<div class="info-label">
<Icon icon="solar:health-bold" height="20" class="mr-2" />
Sub Spesialis
</div>
<div class="info-value">
<v-chip size="small" color="info" variant="flat">
{{ pasienData.rencanaOperasiData?.SubSpesialisName || '-' }}
</v-chip>
</div>
</div>
</v-col>
</v-row>
</v-card-text>
</v-card>
<!-- Form Update Status -->
<v-card elevation="2" class="mb-2">
<v-card-title class="bg-orange-lighten-5 pa-4">
<div class="d-flex align-center">
<Icon icon="solar:settings-bold-duotone" height="24" class="mr-2 text-warning" />
<span class="text-h6">Update Status Operasi</span>
</div>
</v-card-title>
<v-card-text class="pa-4">
<v-row>
<!-- Status Operasi -->
<v-col cols="12">
<v-label class="mb-2 font-weight-medium">
Status Operasi <span class="text-error">*</span>
</v-label>
<v-row class="mt-2">
<v-col cols="12" sm="12">
<v-btn-group
v-model="formData.statusOperasi"
mandatory
class="w-100"
>
<v-btn
class="flex-grow-1"
size="large"
:variant="formData.statusOperasi === STATUS.BELUM ? 'flat' : 'outlined'"
:color="formData.statusOperasi === STATUS.BELUM ? 'primary' : 'default'"
@click="formData.statusOperasi = STATUS.BELUM"
>
<Icon v-if="formData.statusOperasi === STATUS.BELUM" icon="mdi-progress-clock" height="20" class="mr-2" />
Belum
</v-btn>
<v-btn
class="flex-grow-1"
size="large"
:variant="formData.statusOperasi === STATUS.SELESAI ? 'flat' : 'outlined'"
:color="formData.statusOperasi === STATUS.SELESAI ? 'success' : 'default'"
@click="formData.statusOperasi = STATUS.SELESAI"
>
<Icon v-if="formData.statusOperasi === STATUS.SELESAI" icon="mdi-check-circle" height="20" class="mr-2" />
Selesai
</v-btn>
<v-btn
class="flex-grow-1"
size="large"
:variant="formData.statusOperasi === STATUS.TUNDA ? 'flat' : 'outlined'"
:color="formData.statusOperasi === STATUS.TUNDA ? 'warning' : 'default'"
@click="formData.statusOperasi = STATUS.TUNDA"
>
<Icon v-if="formData.statusOperasi === STATUS.TUNDA" icon="mdi-timer-sand" height="20" class="mr-2" />
Tunda
</v-btn>
<v-btn
class="flex-grow-1"
size="large"
:variant="formData.statusOperasi === STATUS.BATAL ? 'flat' : 'outlined'"
:color="formData.statusOperasi === STATUS.BATAL ? 'error' : 'default'"
@click="formData.statusOperasi = STATUS.BATAL"
>
<Icon v-if="formData.statusOperasi === STATUS.BATAL" icon="mdi-close-circle" height="20" class="mr-2" />
Batal
</v-btn>
</v-btn-group>
</v-col>
</v-row>
</v-col>
<!-- Tanggal Selesai (shown only when status is Selesai) -->
<v-col v-if="formData.statusOperasi === STATUS.SELESAI" cols="12">
<v-label class="mb-2 font-weight-medium">
Tanggal Selesai Operasi <span class="text-error">*</span>
</v-label>
<v-text-field
v-model="formData.tanggalSelesai"
type="datetime-local"
variant="outlined"
density="comfortable"
hide-details="auto"
:min="minTanggalSelesai"
:error-messages="!formData.tanggalSelesai ? ['Tanggal selesai wajib diisi'] : []"
></v-text-field>
</v-col>
<!-- Keterangan (shown only when status is Tunda or Batal) -->
<v-col v-if="formData.statusOperasi === STATUS.TUNDA || formData.statusOperasi === STATUS.BATAL" cols="12">
<v-label class="mb-2 font-weight-medium">
Keterangan <span class="text-error">*</span>
</v-label>
<v-textarea
v-model="formData.keteranganStatus"
variant="outlined"
rows="3"
hide-details="auto"
:placeholder="`Masukkan alasan ${formData.statusOperasi === STATUS.TUNDA ? 'penundaan' : 'pembatalan'} operasi`"
:error-messages="!formData.keteranganStatus.trim() ? ['Keterangan wajib diisi'] : []"
></v-textarea>
</v-col>
</v-row>
</v-card-text>
</v-card>
</div>
</v-card-text>
<v-card-actions class="pa-4 justify-end">
<v-btn
color="primary"
size="large"
variant="flat"
prepend-icon="mdi-content-save"
:loading="loadingSubmit"
:disabled="loading || !isFormValid"
@click="handleSubmit"
>
Simpan
</v-btn>
</v-card-actions>
</v-card>
<!-- Snackbar -->
<v-snackbar
v-model="snackbar"
:color="snackbarColor"
:timeout="3000"
location="top"
>
{{ snackbarMessage }}
<template #actions>
<v-btn
variant="text"
@click="snackbar = false"
>
Close
</v-btn>
</template>
</v-snackbar>
</v-dialog>
</template>
<style scoped>
.info-item {
margin-bottom: 1rem;
}
.info-label {
display: flex;
align-items: center;
font-size: 0.875rem;
color: rgb(var(--v-theme-on-surface));
opacity: 0.7;
margin-bottom: 0.5rem;
font-weight: 500;
}
.info-value {
font-size: 1rem;
color: rgb(var(--v-theme-on-surface));
padding-left: 28px;
}
</style>