459 lines
23 KiB
Vue
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>
|