337 lines
11 KiB
Vue
337 lines
11 KiB
Vue
<script setup lang="ts">
|
|
import BaseBreadcrumb from '@/components/shared/BaseBreadcrumb.vue';
|
|
import TableAntrian from '@/components/pendaftaran/TableAntrian.vue';
|
|
import ModalPendaftaran from '@/components/pendaftaran/ModalPendaftaran.vue';
|
|
import ModalDetailPendaftaran from '@/components/pendaftaran/ModalDetailPendaftaran.vue';
|
|
import ModalUpdateStatus from '@/components/pendaftaran/ModalUpdateStatus.vue';
|
|
import { getAntrianOperasi, deleteAntrianOperasi } from '~/services/antrean';
|
|
import { Icon } from '@iconify/vue';
|
|
import type { AntreanOperasi } from '~/types/antrean';
|
|
import { STATUS } from '~/types/antrean';
|
|
|
|
definePageMeta({
|
|
middleware: 'auth',
|
|
pageTitle: 'Semua Antrean Operasi',
|
|
});
|
|
|
|
const search = ref('');
|
|
const statusFilter = ref<string | null>(null);
|
|
const antreanList = ref<any[]>([]);
|
|
const loading = ref(false);
|
|
const currentPage = ref(1);
|
|
const itemsPerPage = ref(10);
|
|
const totalItems = ref(0);
|
|
|
|
const router = useRouter();
|
|
|
|
// Modal state
|
|
const showModal = ref(false);
|
|
const showEditModal = ref(false);
|
|
const showDetailModal = ref(false);
|
|
const showUpdateStatusModal = ref(false);
|
|
const selectedId = ref<string | number | null>(null);
|
|
const selectedIdForEdit = ref<string | number | null>(null);
|
|
const selectedIdForStatus = ref<string | number | null>(null);
|
|
|
|
const statusOptions = [
|
|
{ value: null, label: 'Semua Status' },
|
|
{ value: STATUS.BELUM, label: 'Belum' },
|
|
{ value: STATUS.SELESAI, label: 'Selesai' },
|
|
{ value: STATUS.TUNDA, label: 'Tunda' },
|
|
{ value: STATUS.BATAL, label: 'Batal' }
|
|
];
|
|
|
|
// Fetch data from API
|
|
const fetchData = async () => {
|
|
loading.value = true;
|
|
try {
|
|
const offset = (currentPage.value - 1) * itemsPerPage.value;
|
|
const response = await getAntrianOperasi({
|
|
type: 'all',
|
|
limit: itemsPerPage.value,
|
|
offset: offset,
|
|
status: statusFilter.value || undefined,
|
|
search: search.value || undefined
|
|
});
|
|
|
|
if (response.success) {
|
|
antreanList.value = response.data;
|
|
totalItems.value = response.Paginate.Total;
|
|
}
|
|
} catch (error) {
|
|
console.error('Error fetching antrian operasi:', error);
|
|
showSnackbar('Gagal mengambil data antrian operasi', 'error');
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
};
|
|
|
|
// Watch for page changes
|
|
watch(currentPage, () => {
|
|
fetchData();
|
|
});
|
|
|
|
watch(itemsPerPage, () => {
|
|
currentPage.value = 1;
|
|
fetchData();
|
|
});
|
|
|
|
// Watch for status filter changes
|
|
watch(statusFilter, () => {
|
|
currentPage.value = 1;
|
|
fetchData();
|
|
});
|
|
|
|
// Watch for search with debounce
|
|
let searchTimeout: NodeJS.Timeout | null = null;
|
|
watch(search, () => {
|
|
if (searchTimeout) {
|
|
clearTimeout(searchTimeout);
|
|
}
|
|
searchTimeout = setTimeout(() => {
|
|
currentPage.value = 1;
|
|
fetchData();
|
|
}, 500); // 500ms debounce
|
|
});
|
|
|
|
// Initial fetch
|
|
onMounted(() => {
|
|
fetchData();
|
|
});
|
|
|
|
// Snackbar state
|
|
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;
|
|
};
|
|
|
|
const allHeaders = [
|
|
{ title: 'Nomor', key: 'NoUrutKategori', width: '50px', align: 'center' as const, sortable: false },
|
|
{ title: 'Nomor Spesialis', key: 'NoUrutSpesialis', width: '100px', align: 'center' as const, sortable: false },
|
|
{ title: 'Nomor Sub Spesialis', key: 'NoUrutSubSpesialis', width: '140px', align: 'center' as const, sortable: false },
|
|
{ title: 'Tanggal Daftar', key: 'TglDaftar', sortable: false, width: '150px' },
|
|
{ title: 'No Rekam Medis', key: 'NoRekamMedis', sortable: false, width: '180px' },
|
|
{ title: 'Nama Pasien', key: 'NamaPasien', sortable: false },
|
|
{ title: 'Jenis kelamin', key: 'JenisKelamin', sortable: false },
|
|
{ title: 'Status', key: 'StatusOperasi', sortable: false, width: '150px' },
|
|
{ title: 'Actions', key: 'actions', sortable: false, width: '120px' }
|
|
];
|
|
|
|
// Hide queue number columns when filtering
|
|
const headers = computed(() => {
|
|
const isFiltering = search.value || statusFilter.value;
|
|
if (isFiltering) {
|
|
return allHeaders.filter(header =>
|
|
!['NoUrutKategori', 'NoUrutSpesialis', 'NoUrutSubSpesialis'].includes(header.key)
|
|
);
|
|
}
|
|
return allHeaders;
|
|
});
|
|
|
|
// Function to get actions based on item status
|
|
const getActionsForItem = (item: AntreanOperasi) => {
|
|
const allActions = [
|
|
{ icon: 'mdi-eye', color: 'info', tooltip: 'View', event: 'view' },
|
|
{ icon: 'mdi-pencil', color: 'primary', tooltip: 'Edit', event: 'edit' },
|
|
{ icon: 'mdi-clipboard-check', color: 'success', tooltip: 'Update Status', event: 'updateStatus' },
|
|
{ icon: 'mdi-delete', color: 'error', tooltip: 'Delete', event: 'delete' }
|
|
];
|
|
|
|
// If status is Selesai or Batal, only show View action
|
|
if (item.StatusOperasi === STATUS.SELESAI || item.StatusOperasi === STATUS.BATAL) {
|
|
return allActions.filter(action => action.event === 'view');
|
|
}
|
|
|
|
// For other statuses (Belum, Tunda), show all actions
|
|
return allActions;
|
|
};
|
|
|
|
const handleView = async (item: unknown) => {
|
|
const data = item as AntreanOperasi;
|
|
selectedId.value = data.id;
|
|
// Wait for DOM update before opening modal
|
|
await nextTick();
|
|
showDetailModal.value = true;
|
|
};
|
|
|
|
const handleEdit = async (item: unknown) => {
|
|
const data = item as AntreanOperasi;
|
|
selectedIdForEdit.value = data.id;
|
|
// Wait for DOM update before opening modal
|
|
await nextTick();
|
|
showEditModal.value = true;
|
|
};
|
|
|
|
const handleUpdateStatus = async (item: unknown) => {
|
|
const data = item as AntreanOperasi;
|
|
selectedIdForStatus.value = data.id;
|
|
// Wait for DOM update before opening modal
|
|
await nextTick();
|
|
showUpdateStatusModal.value = true;
|
|
};
|
|
|
|
const handleDelete = async (item: unknown) => {
|
|
const data = item as AntreanOperasi;
|
|
if(confirm('Apakah Anda yakin ingin menghapus data ini?')) {
|
|
try {
|
|
const response = await deleteAntrianOperasi(data.id);
|
|
if (response.success) {
|
|
showSnackbar('Data berhasil dihapus.', 'success');
|
|
fetchData(); // Refresh the table after deletion
|
|
} else {
|
|
showSnackbar(response.message || 'Gagal menghapus data', 'error');
|
|
}
|
|
} catch (error: any) {
|
|
console.error('Error deleting antrian operasi:', error);
|
|
showSnackbar(error.response?.data?.message || 'Gagal menghapus data', 'error');
|
|
}
|
|
}
|
|
};
|
|
|
|
const handlePageUpdate = (page: unknown) => {
|
|
currentPage.value = page as number;
|
|
};
|
|
|
|
const handleItemsPerPageUpdate = (items: unknown) => {
|
|
itemsPerPage.value = items as number;
|
|
};
|
|
|
|
// Modal handlers
|
|
const openModal = () => {
|
|
showModal.value = true;
|
|
};
|
|
|
|
const handleModalSuccess = () => {
|
|
fetchData(); // Refresh the table after successful submission
|
|
};
|
|
|
|
const handleEditModalSuccess = () => {
|
|
fetchData(); // Refresh the table after successful edit
|
|
};
|
|
|
|
const handleUpdateStatusSuccess = () => {
|
|
fetchData(); // Refresh the table after status update
|
|
};
|
|
</script>
|
|
<template>
|
|
<v-row>
|
|
<v-col cols="12">
|
|
<v-card elevation="10">
|
|
<v-card-text>
|
|
<div class="d-flex justify-space-between align-center mb-4">
|
|
<div class="d-flex align-center w-50 ga-4">
|
|
<v-text-field
|
|
v-model="search"
|
|
placeholder="Search..."
|
|
variant="outlined"
|
|
density="compact"
|
|
hide-details
|
|
prepend-inner-icon="mdi-magnify"
|
|
style="max-width: 300px;"
|
|
></v-text-field>
|
|
|
|
|
|
<v-select
|
|
v-model="statusFilter"
|
|
:items="statusOptions"
|
|
item-title="label"
|
|
item-value="value"
|
|
variant="outlined"
|
|
density="compact"
|
|
hide-details
|
|
placeholder="Filter Status"
|
|
style="max-width: 200px;"
|
|
></v-select>
|
|
|
|
</div>
|
|
<v-btn
|
|
color="primary"
|
|
size="large"
|
|
elevation="2"
|
|
@click="openModal"
|
|
>
|
|
<Icon icon="solar:add-circle-bold" height="20" class="mr-2" />
|
|
Pendaftaran Operasi Baru
|
|
</v-btn>
|
|
</div>
|
|
|
|
<TableAntrian
|
|
:headers="headers"
|
|
:items="antreanList"
|
|
:get-actions="getActionsForItem"
|
|
:server-side="true"
|
|
:total-items="totalItems"
|
|
:current-page="currentPage"
|
|
:items-per-page="itemsPerPage"
|
|
:loading="loading"
|
|
min-width="1500px"
|
|
@view="handleView"
|
|
@edit="handleEdit"
|
|
@updateStatus="handleUpdateStatus"
|
|
@delete="handleDelete"
|
|
@update:page="handlePageUpdate"
|
|
@update:itemsPerPage="handleItemsPerPageUpdate"
|
|
>
|
|
<template #item.TglDaftar="{ item }">
|
|
{{ new Date(item.TglDaftar).toLocaleDateString('id-ID', { year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric' }) }}
|
|
</template>
|
|
</TableAntrian>
|
|
</v-card-text>
|
|
</v-card>
|
|
</v-col>
|
|
</v-row>
|
|
|
|
<!-- Snackbar for notifications -->
|
|
<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>
|
|
|
|
<!-- Modal Pendaftaran -->
|
|
<ModalPendaftaran
|
|
v-model="showModal"
|
|
mode="create"
|
|
@success="handleModalSuccess"
|
|
/>
|
|
|
|
<!-- Modal Edit Pendaftaran -->
|
|
<ModalPendaftaran
|
|
v-if="selectedIdForEdit"
|
|
v-model="showEditModal"
|
|
mode="edit"
|
|
:id="selectedIdForEdit"
|
|
@success="handleEditModalSuccess"
|
|
/>
|
|
|
|
<!-- Modal Detail Pendaftaran -->
|
|
<ModalDetailPendaftaran
|
|
v-if="selectedId"
|
|
v-model="showDetailModal"
|
|
:id="selectedId"
|
|
/>
|
|
|
|
<!-- Modal Update Status -->
|
|
<ModalUpdateStatus
|
|
v-if="selectedIdForStatus"
|
|
v-model="showUpdateStatusModal"
|
|
:id="selectedIdForStatus"
|
|
@success="handleUpdateStatusSuccess"
|
|
/>
|
|
</template> |