Files
antrean-operasi/components/pendaftaran/ModalUpdateStatus.vue
T
2026-03-09 08:58:25 +07:00

459 lines
23 KiB
Vue

<script setup lang="ts">
import { Icon } from '@iconify/vue';
import { usePendaftaranStore } from "~/store/pendaftaran";
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: ''
});
// Use pendaftaranStore for global snackbar
const pendaftaranStore = usePendaftaranStore();
const showSnackbar = (message: string, color: string = 'success') => {
pendaftaranStore.showSnackbar(message, color);
};
// 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="1000px" 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: 70vh; 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">
<v-row>
<!-- Left Column: Informasi Pasien -->
<v-col cols="12" md="6">
<v-card elevation="0" class="h-100 border">
<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-subtitle-1 font-weight-bold text-uppercase">Informasi Pasien</span>
</div>
</v-card-title>
<v-card-text class="pa-5">
<!-- Nama Pasien -->
<div class="mb-5">
<div class="text-caption text-medium-emphasis text-uppercase mb-2">Nama Pasien</div>
<div class="text-h6 font-weight-bold">
{{ pasienData.formData?.namaPasien || '-' }}
<v-icon
:color="pasienData.formData?.jenisKelamin === 'L' ? 'info' : 'error'"
size="small"
class="ml-1">
{{ pasienData.formData?.jenisKelamin === 'L' ? 'mdi-gender-male' : 'mdi-gender-female' }}
</v-icon>
</div>
</div>
<!-- No Rekam Medis -->
<div class="mb-5">
<div class="text-caption text-medium-emphasis text-uppercase mb-2">No. Rekam Medis</div>
<div class="text-h6 font-weight-bold">
{{ pasienData.formData?.noRekamMedis || '-' }}
</div>
</div>
<!-- Tanggal Daftar -->
<div class="mb-5">
<div class="text-caption text-medium-emphasis text-uppercase mb-2">
<Icon icon="solar:calendar-bold" height="16" class="mr-1" />
Tanggal Daftar
</div>
<div class="text-body-1">
{{ formatDate(pasienData.rencanaOperasiData?.tanggalDaftar) }}
</div>
</div>
<!-- Kategori -->
<div class="mb-5">
<div class="text-caption text-medium-emphasis text-uppercase mb-2">Kategori</div>
<v-chip variant="flat" size="small">
{{ pasienData.rencanaOperasiData?.kategoriName?.split('-')[1]?.trim() || '-' }}
</v-chip>
</div>
<!-- Spesialis / Sub Spesialis -->
<div>
<div class="text-caption text-medium-emphasis text-uppercase mb-2">Spesialis / Sub Spesialis</div>
<div class="row">
<div class="col-md-12">
<v-chip color="primary" class="mb-1 mr-1" variant="flat" size="small">
{{ pasienData.rencanaOperasiData?.SpesialisName || '-' }}
</v-chip>
<span class="text-body-1">/</span>
<v-chip color="secondary" class="mb-1 ml-1" variant="flat" size="small">
{{ pasienData.rencanaOperasiData?.SubSpesialisName || '-' }}
</v-chip>
</div>
</div>
</div>
</v-card-text>
</v-card>
</v-col>
<!-- Right Column: Update Status Operasi -->
<v-col cols="12" md="6">
<v-card elevation="0" class="h-100 border">
<v-card-title class="bg-blue-lighten-5 pa-4">
<div class="d-flex align-center">
<Icon icon="solar:settings-bold-duotone" height="24" class="mr-2 text-primary" />
<span class="text-subtitle-1 font-weight-bold text-uppercase">Update Status Operasi</span>
</div>
</v-card-title>
<v-card-text class="pa-4 pt-3">
<!-- Status Options -->
<v-list class="pa-0" density="compact">
<!-- Belum -->
<v-list-item
@click="formData.statusOperasi = STATUS.BELUM"
:class="formData.statusOperasi === STATUS.BELUM ? 'bg-primary-lighten-5 border-primary' : 'border'"
class="mb-2 rounded-lg pa-2"
style="border-width: 2px; border-style: solid; cursor: pointer; min-height: 50px;">
<template #prepend>
<div class="status-indicator-sm bg-primary mr-2"></div>
<v-avatar color="grey-lighten-3" size="32" class="mr-2">
<Icon icon="mdi-clock-outline" height="20" class="text-primary" />
</v-avatar>
</template>
<v-list-item-title class="font-weight-bold text-body-1">Belum</v-list-item-title>
<v-list-item-subtitle class="text-caption text-body-2">Pasien masih dalam antrian</v-list-item-subtitle>
<template #append>
<v-icon class="mr-5" v-if="formData.statusOperasi === STATUS.BELUM" color="primary" size="small">
mdi-check-circle
</v-icon>
</template>
</v-list-item>
<!-- Selesai -->
<v-list-item
@click="formData.statusOperasi = STATUS.SELESAI"
:class="formData.statusOperasi === STATUS.SELESAI ? 'bg-success-lighten-5 border-success' : 'border'"
class="mb-2 rounded-lg pa-2"
style="border-width: 2px; border-style: solid; cursor: pointer; min-height: 50px;">
<template #prepend>
<div class="status-indicator-sm bg-success mr-2"></div>
<v-avatar color="success-lighten-4" size="32" class="mr-2">
<Icon icon="mdi-check-circle" height="20" class="text-success" />
</v-avatar>
</template>
<v-list-item-title class="font-weight-bold text-body-1">Selesai</v-list-item-title>
<v-list-item-subtitle class="text-caption text-body-2">Operasi telah berhasil dilakukan</v-list-item-subtitle>
<template #append>
<v-icon class="mr-5" v-if="formData.statusOperasi === STATUS.SELESAI" color="success" size="small">
mdi-check-circle
</v-icon>
</template>
</v-list-item>
<!-- Tunda -->
<v-list-item
@click="formData.statusOperasi = STATUS.TUNDA"
:class="formData.statusOperasi === STATUS.TUNDA ? 'bg-warning-lighten-5 border-warning' : 'border'"
class="mb-2 rounded-lg pa-2"
style="border-width: 2px; border-style: solid; cursor: pointer; min-height: 50px;">
<template #prepend>
<div class="status-indicator-sm bg-warning mr-2"></div>
<v-avatar color="warning-lighten-4" size="32" class="mr-2">
<Icon icon="mdi-pause-circle" height="20" class="text-warning" />
</v-avatar>
</template>
<v-list-item-title class="font-weight-bold text-body-1">Tunda</v-list-item-title>
<v-list-item-subtitle class="text-caption text-body-2" >Jadwal diundur karena alasan medis/teknis</v-list-item-subtitle>
<template #append>
<v-icon class="mr-5" v-if="formData.statusOperasi === STATUS.TUNDA" color="warning" size="small">
mdi-check-circle
</v-icon>
</template>
</v-list-item>
<!-- Batal -->
<v-list-item
@click="formData.statusOperasi = STATUS.BATAL"
:class="formData.statusOperasi === STATUS.BATAL ? 'bg-error-lighten-5 border-error' : 'border'"
class="mb-2 rounded-lg pa-2"
style="border-width: 2px; border-style: solid; cursor: pointer; min-height: 50px;">
<template #prepend>
<div class="status-indicator-sm bg-error mr-2"></div>
<v-avatar color="error-lighten-4" size="32" class="mr-2">
<Icon icon="mdi-close-circle" height="20" class="text-error" />
</v-avatar>
</template>
<v-list-item-title class="font-weight-bold text-body-1">Batal</v-list-item-title>
<v-list-item-subtitle class="text-caption text-body-2" >Operasi dibatalkan sepenuhnya</v-list-item-subtitle>
<template #append>
<v-icon class="mr-5" v-if="formData.statusOperasi === STATUS.BATAL" color="error" size="small">
mdi-check-circle
</v-icon>
</template>
</v-list-item>
</v-list>
<!-- Tanggal Selesai (shown only when status is Selesai) -->
<v-expand-transition>
<div v-if="formData.statusOperasi === STATUS.SELESAI" class="mt-4">
<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>
</div>
</v-expand-transition>
<!-- Keterangan (shown only when status is Tunda or Batal) -->
<v-expand-transition>
<div v-if="formData.statusOperasi === STATUS.TUNDA || formData.statusOperasi === STATUS.BATAL" class="mt-4">
<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>
</div>
</v-expand-transition>
</v-card-text>
</v-card>
</v-col>
</v-row>
</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>
</v-dialog>
</template>
<style scoped>
.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;
}
.status-indicator {
width: 4px;
height: 60px;
border-radius: 4px;
}
.border {
border: 1px solid rgba(0, 0, 0, 0.12);
}
.border-primary {
border-color: rgb(var(--v-theme-primary)) !important;
}
.border-success {
border-color: rgb(var(--v-theme-success)) !important;
}
.border-warning {
border-color: rgb(var(--v-theme-warning)) !important;
}
.border-error {
border-color: rgb(var(--v-theme-error)) !important;
}
</style>