Merge branch 'Antrean-Code' of https://git.rssa.top/arie.bagus.2905/Web-Antrean into Antrean-Code
This commit is contained in:
No files matched your search
@@ -0,0 +1,158 @@
|
||||
<template>
|
||||
<v-menu
|
||||
v-model="menu"
|
||||
:close-on-content-click="false"
|
||||
location="top end"
|
||||
offset="8"
|
||||
origin="bottom right"
|
||||
transition="slide-y-transition"
|
||||
>
|
||||
<template v-slot:activator="{ props: menuProps }">
|
||||
<div
|
||||
v-bind="menuProps"
|
||||
class="d-flex align-center cursor-pointer pa-2 rounded-lg hover-bg"
|
||||
>
|
||||
<v-avatar size="40">
|
||||
<v-img
|
||||
:src="user.picture"
|
||||
:alt="`${user.name} Profile`"
|
||||
></v-img>
|
||||
<v-badge
|
||||
dot
|
||||
color="orange"
|
||||
location="bottom right"
|
||||
offset-x="2"
|
||||
offset-y="2"
|
||||
></v-badge>
|
||||
</v-avatar>
|
||||
|
||||
<div v-show="!rail" class="ml-3 flex-grow-1">
|
||||
<div class="text-subtitle-2 font-weight-bold">
|
||||
{{ user.name }}
|
||||
</div>
|
||||
<div class="text-caption text-grey">
|
||||
{{ user.email }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<v-btn v-show="!rail" icon size="small" variant="text">
|
||||
<v-icon size="20">mdi-dots-vertical</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<v-card width="300" elevation="8" rounded="lg">
|
||||
<v-card-text class="pa-4">
|
||||
<div class="d-flex align-center mb-4">
|
||||
<v-avatar size="48">
|
||||
<v-img :src="user.picture"></v-img>
|
||||
</v-avatar>
|
||||
<div class="ml-3">
|
||||
<div class="text-subtitle-1 font-weight-bold">
|
||||
{{ user.name }}
|
||||
</div>
|
||||
<div class="text-caption text-grey">
|
||||
{{ user.email }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<v-list density="compact" class="pa-0">
|
||||
<v-list-item
|
||||
prepend-icon="mdi-cog-outline"
|
||||
class="px-2 rounded-lg"
|
||||
link
|
||||
@click="handleAction('setting')"
|
||||
>
|
||||
<v-list-item-title class="text-body-2">Setting</v-list-item-title>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item
|
||||
prepend-icon="mdi-help-circle-outline"
|
||||
class="px-2 rounded-lg"
|
||||
link
|
||||
@click="handleAction('help')"
|
||||
>
|
||||
<v-list-item-title class="text-body-2">Bantuan</v-list-item-title>
|
||||
</v-list-item>
|
||||
|
||||
<v-divider class="my-2"></v-divider>
|
||||
|
||||
<v-list-item
|
||||
prepend-icon="mdi-logout"
|
||||
class="px-2 rounded-lg text-red"
|
||||
link
|
||||
@click="signOut"
|
||||
:disabled="isLoggingOut"
|
||||
>
|
||||
<v-list-item-title class="text-body-2">
|
||||
{{ isLoggingOut ? 'Log out...' : 'Log out' }}
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
|
||||
<div class="text-caption text-grey mt-3 text-center">
|
||||
v2.5.18 · Terms & Conditions
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-menu>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
|
||||
// --- PROPS & EMITS ---
|
||||
const props = defineProps({
|
||||
user: {
|
||||
type: Object,
|
||||
// Diubah menjadi required: false dengan default yang aman
|
||||
required: false,
|
||||
default: () => ({
|
||||
name: 'Guest',
|
||||
email: '[email protected]',
|
||||
picture: 'https://i.pravatar.cc/150?img=33'
|
||||
})
|
||||
},
|
||||
rail: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
});
|
||||
const emit = defineEmits(['logout']); // Hanya emit logout
|
||||
|
||||
// --- STATE ---
|
||||
const menu = ref(false);
|
||||
const isLoggingOut = ref(false);
|
||||
|
||||
// --- METHODS ---
|
||||
const signOut = () => {
|
||||
if (isLoggingOut.value) return;
|
||||
isLoggingOut.value = true;
|
||||
menu.value = false;
|
||||
|
||||
// Memicu event untuk ditangani oleh komponen induk (SideBar)
|
||||
emit('logout');
|
||||
// Biarkan parent yang mengurus redirect/state global
|
||||
setTimeout(() => isLoggingOut.value = false, 1000);
|
||||
};
|
||||
|
||||
const handleAction = (action) => {
|
||||
console.log(`[PopupSidebar] Action: ${action} triggered.`);
|
||||
// TODO: Tambahkan logika navigasi/fungsi di sini, misal: router.push('/settings')
|
||||
menu.value = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* Tambahkan kembali style yang relevan untuk aktivator di sini */
|
||||
.hover-bg:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
.text-red {
|
||||
color: rgb(244, 67, 54) !important;
|
||||
}
|
||||
</style>
|
||||
+80
-40
@@ -4,8 +4,7 @@
|
||||
:rail="rail"
|
||||
permanent
|
||||
app
|
||||
class="bg-white"
|
||||
@update:model-value="emit('update:drawer', $event)"
|
||||
class="bg-white d-flex flex-column" @update:model-value="emit('update:drawer', $event)"
|
||||
|
||||
@mouseenter="handleMouseEnter"
|
||||
@mouseleave="handleMouseLeave"
|
||||
@@ -23,6 +22,7 @@
|
||||
</v-list-item>
|
||||
<v-divider></v-divider>
|
||||
|
||||
<<<<<<< HEAD
|
||||
<v-list density="compact" nav class="mt-2" v-model:opened="openedGroups">
|
||||
<template v-for="item in items" :key="item.name">
|
||||
|
||||
@@ -40,53 +40,74 @@
|
||||
active-class="bg-orange-lighten-5 text-orange-darken-2 font-weight-bold"
|
||||
></v-list-item>
|
||||
</template>
|
||||
=======
|
||||
<v-list
|
||||
density="compact"
|
||||
nav
|
||||
class="mt-2 flex-grow-1" v-model:opened="openedGroups"
|
||||
>
|
||||
<template v-for="item in items" :key="item.name">
|
||||
<v-list-group
|
||||
v-if="item.children"
|
||||
:value="item.name"
|
||||
:disabled="rail"
|
||||
>
|
||||
<template v-slot:activator="{ props: groupProps }">
|
||||
<v-list-item
|
||||
v-bind="groupProps"
|
||||
:prepend-icon="item.icon"
|
||||
:title="item.name"
|
||||
color="orange-darken-2"
|
||||
active-class="bg-orange-lighten-5 text-orange-darken-2 font-weight-bold"
|
||||
></v-list-item>
|
||||
</template>
|
||||
>>>>>>> d7f3240a8226d36d66c5dc319c7a665539d5d8e3
|
||||
|
||||
<v-list-item
|
||||
v-for="child in item.children"
|
||||
:key="child.name"
|
||||
:to="child.path"
|
||||
:title="child.name"
|
||||
:prepend-icon="child.icon"
|
||||
link
|
||||
class="pl-8"
|
||||
color="orange-darken-2"
|
||||
active-class="bg-orange-lighten-5 text-orange-darken-2 font-weight-bold"
|
||||
></v-list-item>
|
||||
</v-list-group>
|
||||
<v-list-item
|
||||
v-for="child in item.children"
|
||||
:key="child.name"
|
||||
:to="child.path"
|
||||
:title="child.name"
|
||||
:prepend-icon="child.icon"
|
||||
link
|
||||
class="pl-8"
|
||||
color="orange-darken-2"
|
||||
active-class="bg-orange-lighten-5 text-orange-darken-2 font-weight-bold"
|
||||
></v-list-item>
|
||||
</v-list-group>
|
||||
|
||||
<v-tooltip
|
||||
v-else
|
||||
:disabled="!rail"
|
||||
open-on-hover
|
||||
location="end"
|
||||
:text="item.name"
|
||||
>
|
||||
<template #activator="{ props: tooltipProps }">
|
||||
<v-list-item
|
||||
v-bind="tooltipProps"
|
||||
:prepend-icon="item.icon"
|
||||
:title="item.name"
|
||||
:to="item.path"
|
||||
link
|
||||
color="orange-darken-2"
|
||||
active-class="bg-orange-lighten-5 text-orange-darken-2 font-weight-bold"
|
||||
></v-list-item>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</template>
|
||||
<v-tooltip
|
||||
v-else
|
||||
:disabled="!rail"
|
||||
open-on-hover
|
||||
location="end"
|
||||
:text="item.name"
|
||||
>
|
||||
<template #activator="{ props: tooltipProps }">
|
||||
<v-list-item
|
||||
v-bind="tooltipProps"
|
||||
:prepend-icon="item.icon"
|
||||
:title="item.name"
|
||||
:to="item.path"
|
||||
link
|
||||
color="orange-darken-2"
|
||||
active-class="bg-orange-lighten-5 text-orange-darken-2 font-weight-bold"
|
||||
></v-list-item>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</template>
|
||||
</v-list>
|
||||
|
||||
<v-spacer></v-spacer>
|
||||
<v-divider></v-divider>
|
||||
|
||||
<v-list v-if="user" nav density="compact" class="pa-2">
|
||||
|
||||
<v-list v-if="user" nav density="compact" class="pa-2 flex-shrink-0">
|
||||
<ProfilePopup
|
||||
:user="user"
|
||||
@logout="handleLogout"
|
||||
:rail="rail"
|
||||
/>
|
||||
</v-list>
|
||||
<v-list v-else nav density="compact">
|
||||
<v-list v-else nav density="compact" class="flex-shrink-0">
|
||||
<v-list-item
|
||||
@click="redirectToLogin"
|
||||
prepend-icon="mdi-login"
|
||||
@@ -98,10 +119,14 @@
|
||||
|
||||
</v-navigation-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
<<<<<<< HEAD
|
||||
import { defineProps, defineEmits, onMounted, ref, watch } from 'vue';
|
||||
import { navigateTo, useRoute } from '#app';
|
||||
=======
|
||||
import { defineProps, defineEmits, onMounted, ref, watch } from 'vue';
|
||||
import { navigateTo } from '#app';
|
||||
>>>>>>> d7f3240a8226d36d66c5dc319c7a665539d5d8e3
|
||||
import ProfilePopup from './ProfilePopup.vue';
|
||||
import { useAuth } from '~/composables/useAuth'; // Ensure this path is correct
|
||||
|
||||
@@ -133,14 +158,25 @@ const props = defineProps({
|
||||
|
||||
const emit = defineEmits(['update:drawer', 'toggle-rail']);
|
||||
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
|
||||
>>>>>>> d7f3240a8226d36d66c5dc319c7a665539d5d8e3
|
||||
const openedGroups = ref<string[]>([]);
|
||||
const isHovering = ref(false);
|
||||
|
||||
<<<<<<< HEAD
|
||||
// --- WATCHERS FOR DROPDOWN AND HIGHLIGHTING ---
|
||||
|
||||
// 1. WATCHER: Close dropdowns when sidebar collapses (rail = true)
|
||||
watch(() => props.rail, (newRailState) => {
|
||||
if (newRailState === true) {
|
||||
=======
|
||||
|
||||
watch(() => props.rail, (newRailState) => {
|
||||
if (newRailState === true) {
|
||||
|
||||
>>>>>>> d7f3240a8226d36d66c5dc319c7a665539d5d8e3
|
||||
openedGroups.value = [];
|
||||
}
|
||||
});
|
||||
@@ -165,8 +201,12 @@ watch(() => route.path, (newPath) => {
|
||||
}
|
||||
}, { immediate: true });
|
||||
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
const isHovering = ref(false);
|
||||
>>>>>>> d7f3240a8226d36d66c5dc319c7a665539d5d8e3
|
||||
|
||||
|
||||
// --- HOVER LOGIC for Rail Expansion ---
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
if (props.rail) {
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
// composables/useQueue.js
|
||||
import { ref, computed } from 'vue';
|
||||
import { useQueueStore } from '../stores/queueStore';
|
||||
|
||||
export const useQueue = (adminType = 'loket') => {
|
||||
const queueStore = useQueueStore();
|
||||
|
||||
// Local state
|
||||
const snackbar = ref(false);
|
||||
const snackbarText = ref('');
|
||||
const snackbarColor = ref('success');
|
||||
|
||||
// Dialog states
|
||||
const showKlinikDialog = ref(false);
|
||||
const showPenunjangDialog = ref(false);
|
||||
const showChangeKlinikDialog = ref(false);
|
||||
const klinikSearch = ref('');
|
||||
const penunjangSearch = ref('');
|
||||
const changeKlinikSearch = ref('');
|
||||
const selectedPatientForPenunjang = ref(null);
|
||||
|
||||
// Computed from store - filtered by stage
|
||||
const currentProcessingPatient = queueStore.getCurrentProcessing(adminType);
|
||||
|
||||
// Get patients based on admin type stage - ISOLATED PER ADMIN
|
||||
const stagePatients = computed(() => {
|
||||
return queueStore.getPatientsByStage(adminType).value;
|
||||
});
|
||||
|
||||
// Tabel "Di Loket" - hanya untuk stage admin ini
|
||||
const diLoketPatients = computed(() => stagePatients.value.diLoket);
|
||||
|
||||
// Tabel "Terlambat" - hanya untuk stage admin ini
|
||||
const terlambatPatients = computed(() => stagePatients.value.terlambat);
|
||||
|
||||
// Tabel "Pending" - hanya untuk stage admin ini
|
||||
const pendingPatients = computed(() => stagePatients.value.pending);
|
||||
|
||||
// Pasien waiting - hanya untuk stage admin ini
|
||||
const waitingPatients = computed(() => stagePatients.value.waiting);
|
||||
|
||||
const nextPatient = computed(() => {
|
||||
return waitingPatients.value[0] || null;
|
||||
});
|
||||
|
||||
// Total pasien hanya untuk stage admin ini
|
||||
const totalPasien = computed(() =>
|
||||
queueStore.getTotalPasienByStage(adminType).value
|
||||
);
|
||||
|
||||
const quotaUsed = computed(() => queueStore.quotaUsed);
|
||||
|
||||
// Filtered lists
|
||||
const filteredKliniks = computed(() => {
|
||||
if (!klinikSearch.value) return queueStore.kliniks;
|
||||
return queueStore.kliniks.filter((k) =>
|
||||
k.name.toLowerCase().includes(klinikSearch.value.toLowerCase())
|
||||
);
|
||||
});
|
||||
|
||||
const filteredPenunjangs = computed(() => {
|
||||
if (!penunjangSearch.value) return queueStore.penunjangs;
|
||||
return queueStore.penunjangs.filter((p) =>
|
||||
p.name.toLowerCase().includes(penunjangSearch.value.toLowerCase())
|
||||
);
|
||||
});
|
||||
|
||||
const filteredChangeKliniks = computed(() => {
|
||||
if (!changeKlinikSearch.value) return queueStore.kliniks;
|
||||
return queueStore.kliniks.filter((k) =>
|
||||
k.name.toLowerCase().includes(changeKlinikSearch.value.toLowerCase())
|
||||
);
|
||||
});
|
||||
|
||||
// Methods
|
||||
const showSnackbar = (text, color = 'success') => {
|
||||
snackbarText.value = text;
|
||||
snackbarColor.value = color;
|
||||
snackbar.value = true;
|
||||
};
|
||||
|
||||
const callNext = () => {
|
||||
const result = queueStore.callNext(adminType);
|
||||
showSnackbar(result.message, result.success ? 'success' : 'warning');
|
||||
};
|
||||
|
||||
const callMultiplePatients = (count) => {
|
||||
const result = queueStore.callMultiplePatients(count, adminType);
|
||||
showSnackbar(result.message, result.success ? 'success' : 'warning');
|
||||
};
|
||||
|
||||
const processPatient = (patient, action) => {
|
||||
const result = queueStore.processPatient(patient, action, adminType);
|
||||
|
||||
let color = 'success';
|
||||
if (action === 'terlambat') color = 'warning';
|
||||
else if (action === 'pending') color = 'info';
|
||||
|
||||
showSnackbar(result.message, color);
|
||||
};
|
||||
|
||||
const selectKlinik = (klinik) => {
|
||||
const result = queueStore.createAntreanKlinik(klinik, adminType);
|
||||
showSnackbar(result.message, 'success');
|
||||
showKlinikDialog.value = false;
|
||||
};
|
||||
|
||||
const selectPenunjang = (penunjang) => {
|
||||
const result = queueStore.createAntreanPenunjang(
|
||||
penunjang,
|
||||
selectedPatientForPenunjang.value,
|
||||
adminType
|
||||
);
|
||||
showSnackbar(result.message, 'success');
|
||||
showPenunjangDialog.value = false;
|
||||
selectedPatientForPenunjang.value = null;
|
||||
};
|
||||
|
||||
const openPenunjangDialog = (patient = null) => {
|
||||
selectedPatientForPenunjang.value = patient;
|
||||
showPenunjangDialog.value = true;
|
||||
};
|
||||
|
||||
const changeKlinik = (klinik) => {
|
||||
if (currentProcessingPatient.value) {
|
||||
const result = queueStore.changeKlinik(
|
||||
currentProcessingPatient.value,
|
||||
klinik,
|
||||
adminType
|
||||
);
|
||||
showSnackbar(result.message, result.success ? 'success' : 'error');
|
||||
showChangeKlinikDialog.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const getRowClass = (item) => {
|
||||
if (item.status === 'current') {
|
||||
return 'text-success font-weight-bold';
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
return {
|
||||
// State
|
||||
snackbar,
|
||||
snackbarText,
|
||||
snackbarColor,
|
||||
showKlinikDialog,
|
||||
showPenunjangDialog,
|
||||
showChangeKlinikDialog,
|
||||
klinikSearch,
|
||||
penunjangSearch,
|
||||
changeKlinikSearch,
|
||||
selectedPatientForPenunjang,
|
||||
|
||||
// Computed
|
||||
currentProcessingPatient,
|
||||
diLoketPatients,
|
||||
terlambatPatients,
|
||||
pendingPatients,
|
||||
waitingPatients,
|
||||
nextPatient,
|
||||
totalPasien,
|
||||
quotaUsed,
|
||||
filteredKliniks,
|
||||
filteredPenunjangs,
|
||||
filteredChangeKliniks,
|
||||
stagePatients,
|
||||
|
||||
// Methods
|
||||
showSnackbar,
|
||||
callNext,
|
||||
callMultiplePatients,
|
||||
processPatient,
|
||||
selectKlinik,
|
||||
selectPenunjang,
|
||||
openPenunjangDialog,
|
||||
changeKlinik,
|
||||
getRowClass,
|
||||
};
|
||||
};
|
||||
+1
-1
@@ -21,7 +21,7 @@ export default defineNuxtConfig({
|
||||
'@nuxt/test-utils',
|
||||
'@nuxt/ui',
|
||||
'@pinia/nuxt',
|
||||
'nuxt-qrcode',
|
||||
|
||||
// Remove '@sidebase/nuxt-auth' completely
|
||||
(_options, nuxt) => {
|
||||
nuxt.hooks.hook('vite:extendConfig', (config) => {
|
||||
|
||||
@@ -0,0 +1,746 @@
|
||||
<!-- pages/AdminKlinik.vue -->
|
||||
<template>
|
||||
<div class="loket-container">
|
||||
<!-- Header Section -->
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<div class="header-left">
|
||||
<div class="header-icon">
|
||||
<v-icon size="32" color="white">mdi-hospital-building</v-icon>
|
||||
</div>
|
||||
<div class="header-text">
|
||||
<h1 class="page-title">Klinik Admin</h1>
|
||||
<p class="page-subtitle">Rabu, 13 Agustus 2025 - Pelayanan</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<v-chip color="success" variant="flat" class="mr-2">
|
||||
Total {{ totalPasien }} Pasien
|
||||
</v-chip>
|
||||
<v-chip color="white" variant="flat" class="text-primary">
|
||||
Max 150 Pasien
|
||||
</v-chip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Combined Control Section -->
|
||||
<v-row class="mb-4">
|
||||
<!-- Left Side: Current Processing -->
|
||||
<v-col cols="12" lg="6">
|
||||
<!-- Current Patient Processing Card -->
|
||||
<v-card class="current-processing-card" elevation="2">
|
||||
<v-card-text class="pa-4">
|
||||
<!-- State when there's a patient being processed -->
|
||||
<div
|
||||
v-if="currentProcessingPatient"
|
||||
class="d-flex align-center justify-space-between"
|
||||
>
|
||||
<div class="patient-info">
|
||||
<v-chip color="primary" variant="flat" class="mb-2">
|
||||
<v-icon start>mdi-account-clock</v-icon>
|
||||
SEDANG DIPROSES
|
||||
</v-chip>
|
||||
<div class="text-h5 font-weight-bold mb-1">
|
||||
{{ currentProcessingPatient.noAntrian.split(" |")[0] }}
|
||||
</div>
|
||||
<div class="text-subtitle-1 text-grey-darken-1">
|
||||
{{ currentProcessingPatient.barcode }} |
|
||||
{{ currentProcessingPatient.klinik }}
|
||||
</div>
|
||||
<div class="patient-meta">
|
||||
Jenis Pembayaran: {{ currentProcessingPatient.pembayaran }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="action-buttons d-flex flex-column">
|
||||
<v-btn
|
||||
color="success"
|
||||
variant="flat"
|
||||
class="mb-2"
|
||||
width="120"
|
||||
@click="processPatient(currentProcessingPatient, 'check-in')"
|
||||
>
|
||||
<v-icon start>mdi-check-circle</v-icon>
|
||||
Check In
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="warning"
|
||||
variant="flat"
|
||||
class="mb-2"
|
||||
width="120"
|
||||
@click="processPatient(currentProcessingPatient, 'terlambat')"
|
||||
>
|
||||
<v-icon start>mdi-clock-alert</v-icon>
|
||||
Terlambat
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="error"
|
||||
variant="flat"
|
||||
class="mb-2"
|
||||
width="120"
|
||||
@click="processPatient(currentProcessingPatient, 'pending')"
|
||||
>
|
||||
<v-icon start>mdi-pause-circle</v-icon>
|
||||
Pending
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="info"
|
||||
variant="flat"
|
||||
width="120"
|
||||
@click="showChangeKlinikDialog = true"
|
||||
>
|
||||
<v-icon start>mdi-swap-horizontal</v-icon>
|
||||
Ubah Klinik
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Empty state when no patient is being processed -->
|
||||
<div v-else class="text-center pa-6">
|
||||
<v-icon size="64" color="grey-lighten-1" class="mb-3">
|
||||
mdi-account-off-outline
|
||||
</v-icon>
|
||||
<div class="text-h6 text-grey-darken-1 mb-2">
|
||||
Tidak Ada Pasien yang Diproses
|
||||
</div>
|
||||
<div class="text-body-2 text-grey">
|
||||
Pasien akan muncul otomatis saat check-in dari loket atau buat antrean klinik baru
|
||||
</div>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<!-- Right Side: Queue Info and Call Controls -->
|
||||
<v-col cols="12" lg="6">
|
||||
<v-card class="quota-call-card" elevation="2">
|
||||
<v-card-text class="pa-4">
|
||||
<div class="d-flex align-center justify-center mb-3">
|
||||
<v-chip color="success" variant="flat" size="large">
|
||||
<v-icon start>mdi-account-arrow-right</v-icon>
|
||||
PANGGIL ANTREAN KLINIK
|
||||
</v-chip>
|
||||
</div>
|
||||
|
||||
<v-row class="mb-3">
|
||||
<v-col cols="6" class="text-center">
|
||||
<div class="quota-label">Kuota</div>
|
||||
<div class="quota-number">150</div>
|
||||
</v-col>
|
||||
<v-col cols="6" class="text-center">
|
||||
<div class="quota-label">Tersedia</div>
|
||||
<div class="quota-number quota-available">
|
||||
{{ 150 - quotaUsed }}
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<div class="text-center mb-3">
|
||||
<div class="quota-used-text">
|
||||
Total Quota Terpakai: {{ quotaUsed }}
|
||||
</div>
|
||||
<v-progress-linear
|
||||
:model-value="(quotaUsed / 150) * 100"
|
||||
color="success"
|
||||
height="6"
|
||||
rounded
|
||||
class="mt-2"
|
||||
></v-progress-linear>
|
||||
</div>
|
||||
|
||||
<v-divider class="my-3"></v-divider>
|
||||
|
||||
<div class="call-controls">
|
||||
<div class="call-label mb-2">Kumulatif Panggil Antrean</div>
|
||||
<div class="call-buttons">
|
||||
<v-btn
|
||||
color="success"
|
||||
variant="flat"
|
||||
class="call-btn"
|
||||
@click="callNext"
|
||||
:disabled="!nextPatient"
|
||||
>
|
||||
<span class="call-btn-text">1</span>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="info"
|
||||
variant="flat"
|
||||
class="call-btn"
|
||||
@click="callMultiplePatients(5)"
|
||||
>
|
||||
<span class="call-btn-text">5</span>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="warning"
|
||||
variant="flat"
|
||||
class="call-btn"
|
||||
@click="callMultiplePatients(10)"
|
||||
>
|
||||
<span class="call-btn-text">10</span>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="error"
|
||||
variant="flat"
|
||||
class="call-btn"
|
||||
@click="callMultiplePatients(20)"
|
||||
>
|
||||
<span class="call-btn-text">20</span>
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row class="mb-4">
|
||||
<v-col cols="12">
|
||||
<div class="d-flex justify-center gap-2">
|
||||
<v-btn
|
||||
color="primary"
|
||||
variant="flat"
|
||||
size="large"
|
||||
@click="showKlinikDialog = true"
|
||||
>
|
||||
<v-icon start>mdi-hospital-building</v-icon>
|
||||
Buat Antrean Klinik Ruang
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="secondary"
|
||||
variant="flat"
|
||||
size="large"
|
||||
@click="openPenunjangDialog()"
|
||||
>
|
||||
<v-icon start>mdi-clipboard-pulse</v-icon>
|
||||
Buat Antrean Penunjang
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- Di Loket Patients Table -->
|
||||
<v-card class="main-table-card mb-4" elevation="2">
|
||||
<TabelData
|
||||
:headers="diLoketHeaders"
|
||||
:items="diLoketPatients"
|
||||
title="DATA PASIEN - DI KLINIK"
|
||||
>
|
||||
<template #actions="{ item }">
|
||||
<div class="d-flex gap-1">
|
||||
<v-btn
|
||||
size="small"
|
||||
color="info"
|
||||
variant="flat"
|
||||
@click="processPatient(item, 'aktifkan')"
|
||||
>
|
||||
Aktifkan
|
||||
</v-btn>
|
||||
<v-btn
|
||||
size="small"
|
||||
color="success"
|
||||
variant="flat"
|
||||
@click="processPatient(item, 'proses')"
|
||||
>
|
||||
Proses
|
||||
</v-btn>
|
||||
</div>
|
||||
</template>
|
||||
</TabelData>
|
||||
</v-card>
|
||||
|
||||
<!-- Terlambat Patients Table -->
|
||||
<v-card
|
||||
class="late-table-card mb-4"
|
||||
elevation="2"
|
||||
v-if="terlambatPatients.length > 0"
|
||||
>
|
||||
<TabelData
|
||||
:headers="terlambatHeaders"
|
||||
:items="terlambatPatients"
|
||||
title="INFO PASIEN LAPOR TERLAMBAT"
|
||||
>
|
||||
<template #actions="{ item }">
|
||||
<div class="d-flex gap-1">
|
||||
<v-btn
|
||||
size="small"
|
||||
color="success"
|
||||
variant="flat"
|
||||
@click="processPatient(item, 'aktifkan')"
|
||||
>
|
||||
Aktifkan
|
||||
</v-btn>
|
||||
</div>
|
||||
</template>
|
||||
</TabelData>
|
||||
</v-card>
|
||||
|
||||
<!-- Pending Patients Table -->
|
||||
<v-card
|
||||
class="pending-table-card mb-4"
|
||||
elevation="2"
|
||||
v-if="pendingPatients.length > 0"
|
||||
>
|
||||
<TabelData
|
||||
:headers="pendingHeaders"
|
||||
:items="pendingPatients"
|
||||
title="INFO PASIEN PENDING"
|
||||
>
|
||||
<template #actions="{ item }">
|
||||
<div class="d-flex gap-1">
|
||||
<v-btn
|
||||
size="small"
|
||||
color="success"
|
||||
variant="flat"
|
||||
@click="processPatient(item, 'proses')"
|
||||
>
|
||||
Proses
|
||||
</v-btn>
|
||||
</div>
|
||||
</template>
|
||||
</TabelData>
|
||||
</v-card>
|
||||
|
||||
<!-- Klinik Dialog -->
|
||||
<v-dialog v-model="showKlinikDialog" max-width="900px">
|
||||
<v-card>
|
||||
<v-card-title class="d-flex align-center pa-4 bg-primary text-white">
|
||||
<v-btn icon @click="showKlinikDialog = false" class="mr-2">
|
||||
<v-icon color="white">mdi-arrow-left</v-icon>
|
||||
</v-btn>
|
||||
<span class="text-h6">Buat Antrean Klinik Ruang</span>
|
||||
</v-card-title>
|
||||
|
||||
<v-card-subtitle class="pa-4 text-center">
|
||||
<h3 class="text-h6 mb-3">Pilih Klinik</h3>
|
||||
<div class="d-flex justify-end gap-2">
|
||||
<v-text-field
|
||||
v-model="klinikSearch"
|
||||
placeholder="Pencarian"
|
||||
density="compact"
|
||||
hide-details
|
||||
style="max-width: 200px"
|
||||
></v-text-field>
|
||||
</div>
|
||||
</v-card-subtitle>
|
||||
|
||||
<v-card-text class="pa-4">
|
||||
<v-row>
|
||||
<v-col
|
||||
v-for="klinik in filteredKliniks"
|
||||
:key="klinik.id"
|
||||
cols="12"
|
||||
sm="6"
|
||||
md="4"
|
||||
>
|
||||
<v-card
|
||||
class="klinik-card"
|
||||
elevation="1"
|
||||
@click="selectKlinik(klinik)"
|
||||
>
|
||||
<v-card-text class="text-center pa-4">
|
||||
<div class="text-h6 font-weight-bold">{{ klinik.name }}</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- Penunjang Dialog -->
|
||||
<v-dialog v-model="showPenunjangDialog" max-width="900px">
|
||||
<v-card>
|
||||
<v-card-title class="d-flex align-center pa-4 bg-secondary text-white">
|
||||
<v-btn icon @click="showPenunjangDialog = false" class="mr-2">
|
||||
<v-icon color="white">mdi-arrow-left</v-icon>
|
||||
</v-btn>
|
||||
<span class="text-h6">Buat Antrean Penunjang</span>
|
||||
</v-card-title>
|
||||
|
||||
<v-card-subtitle class="pa-4">
|
||||
<h3 class="text-h6 mb-3 text-center">Pilih Ruang</h3>
|
||||
<div class="d-flex justify-end gap-2">
|
||||
<v-text-field
|
||||
v-model="penunjangSearch"
|
||||
placeholder="Pencarian"
|
||||
density="compact"
|
||||
hide-details
|
||||
style="max-width: 200px"
|
||||
></v-text-field>
|
||||
</div>
|
||||
</v-card-subtitle>
|
||||
|
||||
<v-card-text class="pa-4">
|
||||
<v-row>
|
||||
<v-col
|
||||
v-for="penunjang in filteredPenunjangs"
|
||||
:key="penunjang.id"
|
||||
cols="12"
|
||||
sm="6"
|
||||
md="4"
|
||||
>
|
||||
<v-card
|
||||
class="penunjang-card"
|
||||
elevation="1"
|
||||
@click="selectPenunjang(penunjang)"
|
||||
>
|
||||
<v-card-text class="text-center pa-4">
|
||||
<div class="text-h6 font-weight-bold">
|
||||
{{ penunjang.name }}
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- Change Klinik Dialog -->
|
||||
<v-dialog v-model="showChangeKlinikDialog" max-width="900px">
|
||||
<v-card>
|
||||
<v-card-title class="d-flex align-center pa-4 bg-info text-white">
|
||||
<v-btn icon @click="showChangeKlinikDialog = false" class="mr-2">
|
||||
<v-icon color="white">mdi-arrow-left</v-icon>
|
||||
</v-btn>
|
||||
<span class="text-h6">Ubah Klinik</span>
|
||||
</v-card-title>
|
||||
|
||||
<v-card-subtitle class="pa-4">
|
||||
<h3 class="text-h6 mb-3 text-center">Pilih Klinik Baru</h3>
|
||||
<div class="d-flex justify-end gap-2">
|
||||
<v-text-field
|
||||
v-model="changeKlinikSearch"
|
||||
placeholder="Pencarian"
|
||||
density="compact"
|
||||
hide-details
|
||||
style="max-width: 200px"
|
||||
></v-text-field>
|
||||
</div>
|
||||
</v-card-subtitle>
|
||||
|
||||
<v-card-text class="pa-4">
|
||||
<v-row>
|
||||
<v-col
|
||||
v-for="klinik in filteredChangeKliniks"
|
||||
:key="klinik.id"
|
||||
cols="12"
|
||||
sm="6"
|
||||
md="4"
|
||||
>
|
||||
<v-card
|
||||
class="klinik-card"
|
||||
elevation="1"
|
||||
@click="changeKlinik(klinik)"
|
||||
>
|
||||
<v-card-text class="text-center pa-4">
|
||||
<div class="text-h6 font-weight-bold">{{ klinik.name }}</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- Snackbar -->
|
||||
<v-snackbar
|
||||
v-model="snackbar"
|
||||
:color="snackbarColor"
|
||||
:timeout="3000"
|
||||
location="top right"
|
||||
>
|
||||
{{ snackbarText }}
|
||||
<template v-slot:actions>
|
||||
<v-btn icon @click="snackbar = false">
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import TabelData from "../components/TabelData.vue";
|
||||
import { useQueue } from "../composables/useQueue";
|
||||
|
||||
// Use queue composable with 'klinik' admin type
|
||||
const {
|
||||
snackbar,
|
||||
snackbarText,
|
||||
snackbarColor,
|
||||
showKlinikDialog,
|
||||
showPenunjangDialog,
|
||||
showChangeKlinikDialog,
|
||||
klinikSearch,
|
||||
penunjangSearch,
|
||||
changeKlinikSearch,
|
||||
currentProcessingPatient,
|
||||
diLoketPatients,
|
||||
terlambatPatients,
|
||||
pendingPatients,
|
||||
nextPatient,
|
||||
totalPasien,
|
||||
quotaUsed,
|
||||
filteredKliniks,
|
||||
filteredPenunjangs,
|
||||
filteredChangeKliniks,
|
||||
callNext,
|
||||
callMultiplePatients,
|
||||
processPatient,
|
||||
selectKlinik,
|
||||
selectPenunjang,
|
||||
openPenunjangDialog,
|
||||
changeKlinik,
|
||||
} = useQueue('klinik');
|
||||
|
||||
const diLoketHeaders = ref([
|
||||
{ title: "No", value: "no", sortable: false, width: "60px" },
|
||||
{ title: "Jam Panggil", value: "jamPanggil", sortable: true, width: "100px" },
|
||||
{ title: "Barcode", value: "barcode", sortable: true, width: "140px" },
|
||||
{ title: "No Antrian", value: "noAntrian", sortable: true, width: "200px" },
|
||||
{ title: "Shift", value: "shift", sortable: true, width: "80px" },
|
||||
{ title: "Klinik", value: "klinik", sortable: true, width: "120px" },
|
||||
{ title: "Fast Track", value: "fastTrack", sortable: true, width: "100px" },
|
||||
{ title: "Pembayaran", value: "pembayaran", sortable: true, width: "100px" },
|
||||
{ title: "Aksi", value: "aksi", sortable: false, width: "200px" },
|
||||
]);
|
||||
|
||||
const terlambatHeaders = ref([
|
||||
{ title: "No", value: "no", sortable: false, width: "60px" },
|
||||
{ title: "Barcode", value: "barcode", sortable: true, width: "140px" },
|
||||
{ title: "No Antrian", value: "noAntrian", sortable: true, width: "200px" },
|
||||
{ title: "Shift", value: "shift", sortable: true, width: "80px" },
|
||||
{ title: "Klinik", value: "klinik", sortable: true, width: "120px" },
|
||||
{ title: "Aksi", value: "aksi", sortable: false, width: "100px" },
|
||||
]);
|
||||
|
||||
const pendingHeaders = ref([
|
||||
{ title: "#", value: "no", sortable: false, width: "60px" },
|
||||
{ title: "Barcode", value: "barcode", sortable: true, width: "140px" },
|
||||
{ title: "No Antrian", value: "noAntrian", sortable: true, width: "200px" },
|
||||
{ title: "Shift", value: "shift", sortable: true, width: "80px" },
|
||||
{ title: "Klinik", value: "klinik", sortable: true, width: "120px" },
|
||||
{ title: "Fast Track", value: "fastTrack", sortable: true, width: "100px" },
|
||||
{ title: "Pembayaran", value: "pembayaran", sortable: true, width: "100px" },
|
||||
{ title: "Aksi", value: "aksi", sortable: false, width: "100px" },
|
||||
]);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.loket-container {
|
||||
background: #f5f7fa;
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
background: linear-gradient(135deg, #f57c00 0%, #e65100 100%);
|
||||
border-radius: 16px;
|
||||
margin-bottom: 24px;
|
||||
box-shadow: 0 8px 32px rgba(245, 124, 0, 0.3);
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 32px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 16px;
|
||||
padding: 16px;
|
||||
margin-right: 20px;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
margin: 4px 0 0 0;
|
||||
opacity: 0.9;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.current-processing-card,
|
||||
.quota-call-card,
|
||||
.main-table-card,
|
||||
.late-table-card,
|
||||
.pending-table-card {
|
||||
border-radius: 16px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.current-processing-card {
|
||||
background: linear-gradient(135deg, #fff3e0 0%, #ffe0b2 100%);
|
||||
border: 2px solid rgba(255, 152, 0, 0.2);
|
||||
}
|
||||
|
||||
.quota-call-card {
|
||||
background: linear-gradient(135deg, #e3f2fd 0%, #f5f5f5 100%);
|
||||
border: 2px solid rgba(33, 150, 243, 0.2);
|
||||
}
|
||||
|
||||
.patient-info .text-h5 {
|
||||
color: #1976d2;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.quota-label {
|
||||
font-size: 13px;
|
||||
color: #6b7280;
|
||||
font-weight: 500;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.quota-number {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
color: #1f2937;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.quota-available {
|
||||
color: #4caf50;
|
||||
}
|
||||
|
||||
.quota-used-text {
|
||||
font-size: 13px;
|
||||
color: #6b7280;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.call-controls {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.call-label {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.call-buttons {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.call-btn {
|
||||
min-width: 80px;
|
||||
height: 48px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.call-btn-text {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.klinik-card,
|
||||
.penunjang-card {
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.klinik-card:hover,
|
||||
.penunjang-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.v-btn {
|
||||
text-transform: none !important;
|
||||
}
|
||||
|
||||
.v-btn--size-small {
|
||||
height: 32px;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.text-success {
|
||||
color: #4caf50 !important;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.header-content {
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.action-buttons .v-btn {
|
||||
flex: 1;
|
||||
min-width: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.loket-container {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
padding: 24px 20px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.action-buttons .v-btn {
|
||||
width: 100%;
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.quota-number {
|
||||
font-size: 28px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,749 @@
|
||||
<!-- pages/LoketAdmin.vue -->
|
||||
<template>
|
||||
<div class="loket-container">
|
||||
<!-- Header Section -->
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<div class="header-left">
|
||||
<div class="header-icon">
|
||||
<v-icon size="32" color="white">mdi-view-dashboard</v-icon>
|
||||
</div>
|
||||
<div class="header-text">
|
||||
<h1 class="page-title">Loket Admin</h1>
|
||||
<p class="page-subtitle">Rabu, 13 Agustus 2025 - Pelayanan</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<v-chip color="success" variant="flat" class="mr-2">
|
||||
Total {{ totalPasien }} Pasien
|
||||
</v-chip>
|
||||
<v-chip color="white" variant="flat" class="text-primary">
|
||||
Max 150 Pasien
|
||||
</v-chip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Combined Control Section -->
|
||||
<v-row class="mb-4">
|
||||
<!-- Left Side: Current Processing -->
|
||||
<v-col cols="12" lg="6">
|
||||
<!-- Current Patient Processing Card -->
|
||||
<v-card
|
||||
v-if="currentProcessingPatient"
|
||||
class="current-processing-card"
|
||||
elevation="2"
|
||||
>
|
||||
<v-card-text class="pa-4">
|
||||
<div class="d-flex align-center justify-space-between">
|
||||
<div class="patient-info">
|
||||
<v-chip color="primary" variant="flat" class="mb-2">
|
||||
<v-icon start>mdi-account-clock</v-icon>
|
||||
SEDANG DIPROSES
|
||||
</v-chip>
|
||||
<div class="text-h5 font-weight-bold mb-1">
|
||||
{{ currentProcessingPatient.noAntrian.split(" |")[0] }}
|
||||
</div>
|
||||
<div class="text-subtitle-1 text-grey-darken-1">
|
||||
{{ currentProcessingPatient.barcode }} |
|
||||
{{ currentProcessingPatient.klinik }}
|
||||
</div>
|
||||
<div class="patient-meta">
|
||||
Jenis Pembayaran: {{ currentProcessingPatient.pembayaran }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="action-buttons d-flex flex-column">
|
||||
<v-btn
|
||||
color="success"
|
||||
variant="flat"
|
||||
class="mb-2"
|
||||
width="120"
|
||||
@click="processPatient(currentProcessingPatient, 'check-in')"
|
||||
>
|
||||
<v-icon start>mdi-check-circle</v-icon>
|
||||
Check In
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="warning"
|
||||
variant="flat"
|
||||
class="mb-2"
|
||||
width="120"
|
||||
@click="processPatient(currentProcessingPatient, 'terlambat')"
|
||||
>
|
||||
<v-icon start>mdi-clock-alert</v-icon>
|
||||
Terlambat
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="error"
|
||||
variant="flat"
|
||||
class="mb-2"
|
||||
width="120"
|
||||
@click="processPatient(currentProcessingPatient, 'pending')"
|
||||
>
|
||||
<v-icon start>mdi-pause-circle</v-icon>
|
||||
Pending
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="info"
|
||||
variant="flat"
|
||||
width="120"
|
||||
@click="showChangeKlinikDialog = true"
|
||||
>
|
||||
<v-icon start>mdi-swap-horizontal</v-icon>
|
||||
Ubah Klinik
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<!-- Empty State -->
|
||||
<v-card v-else class="current-processing-card" elevation="2">
|
||||
<v-card-text class="pa-6 text-center">
|
||||
<v-icon size="64" color="grey-lighten-1" class="mb-3">
|
||||
mdi-account-off-outline
|
||||
</v-icon>
|
||||
<div class="text-h6 text-grey-darken-1 mb-2">
|
||||
Tidak Ada Pasien yang Diproses
|
||||
</div>
|
||||
<div class="text-body-2 text-grey">
|
||||
Pilih pasien dari tabel "Di Loket" untuk memproses
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<!-- Right Side: Queue Info and Call Controls -->
|
||||
<v-col cols="12" lg="6">
|
||||
<v-card class="quota-call-card" elevation="2">
|
||||
<v-card-text class="pa-4">
|
||||
<div class="d-flex align-center justify-center mb-3">
|
||||
<v-chip color="success" variant="flat" size="large">
|
||||
<v-icon start>mdi-account-arrow-right</v-icon>
|
||||
PANGGIL ANTREAN ANJUNGAN
|
||||
</v-chip>
|
||||
</div>
|
||||
|
||||
<v-row class="mb-3">
|
||||
<v-col cols="6" class="text-center">
|
||||
<div class="quota-label">Kuota</div>
|
||||
<div class="quota-number">150</div>
|
||||
</v-col>
|
||||
<v-col cols="6" class="text-center">
|
||||
<div class="quota-label">Tersedia</div>
|
||||
<div class="quota-number quota-available">
|
||||
{{ 150 - quotaUsed }}
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<div class="text-center mb-3">
|
||||
<div class="quota-used-text">
|
||||
Total Quota Terpakai: {{ quotaUsed }}
|
||||
</div>
|
||||
<v-progress-linear
|
||||
:model-value="(quotaUsed / 150) * 100"
|
||||
color="success"
|
||||
height="6"
|
||||
rounded
|
||||
class="mt-2"
|
||||
></v-progress-linear>
|
||||
</div>
|
||||
|
||||
<v-divider class="my-3"></v-divider>
|
||||
|
||||
<div class="call-controls">
|
||||
<div class="call-label mb-2">Kumulatif Panggil Antrean</div>
|
||||
<div class="call-buttons">
|
||||
<v-btn
|
||||
color="success"
|
||||
variant="flat"
|
||||
class="call-btn"
|
||||
@click="callNext"
|
||||
:disabled="!nextPatient"
|
||||
>
|
||||
<span class="call-btn-text">1</span>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="info"
|
||||
variant="flat"
|
||||
class="call-btn"
|
||||
@click="callMultiplePatients(5)"
|
||||
>
|
||||
<span class="call-btn-text">5</span>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="warning"
|
||||
variant="flat"
|
||||
class="call-btn"
|
||||
@click="callMultiplePatients(10)"
|
||||
>
|
||||
<span class="call-btn-text">10</span>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="error"
|
||||
variant="flat"
|
||||
class="call-btn"
|
||||
@click="callMultiplePatients(20)"
|
||||
>
|
||||
<span class="call-btn-text">20</span>
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row class="mb-4">
|
||||
<v-col cols="12">
|
||||
<div class="d-flex justify-center gap-2">
|
||||
<v-btn
|
||||
color="primary"
|
||||
variant="flat"
|
||||
size="large"
|
||||
@click="showKlinikDialog = true"
|
||||
>
|
||||
<v-icon start>mdi-hospital-building</v-icon>
|
||||
Buat Antrean Klinik Ruang
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="secondary"
|
||||
variant="flat"
|
||||
size="large"
|
||||
@click="openPenunjangDialog()"
|
||||
>
|
||||
<v-icon start>mdi-clipboard-pulse</v-icon>
|
||||
Buat Antrean Penunjang
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- Di Loket Patients Table -->
|
||||
<v-card class="main-table-card mb-4" elevation="2">
|
||||
<TabelData
|
||||
:headers="diLoketHeaders"
|
||||
:items="diLoketPatients"
|
||||
title="DATA PASIEN - DI LOKET"
|
||||
>
|
||||
<template #actions="{ item }">
|
||||
<div class="d-flex gap-1">
|
||||
<v-btn
|
||||
size="small"
|
||||
color="info"
|
||||
variant="flat"
|
||||
@click="processPatient(item, 'aktifkan')"
|
||||
>
|
||||
Aktifkan
|
||||
</v-btn>
|
||||
<v-btn
|
||||
size="small"
|
||||
color="success"
|
||||
variant="flat"
|
||||
@click="processPatient(item, 'proses')"
|
||||
>
|
||||
Proses
|
||||
</v-btn>
|
||||
</div>
|
||||
</template>
|
||||
</TabelData>
|
||||
</v-card>
|
||||
|
||||
<!-- Terlambat Patients Table -->
|
||||
<v-card
|
||||
class="late-table-card mb-4"
|
||||
elevation="2"
|
||||
v-if="terlambatPatients.length > 0"
|
||||
>
|
||||
<TabelData
|
||||
:headers="terlambatHeaders"
|
||||
:items="terlambatPatients"
|
||||
title="INFO PASIEN LAPOR TERLAMBAT"
|
||||
>
|
||||
<template #actions="{ item }">
|
||||
<div class="d-flex gap-1">
|
||||
<v-btn
|
||||
size="small"
|
||||
color="success"
|
||||
variant="flat"
|
||||
@click="processPatient(item, 'aktifkan')"
|
||||
>
|
||||
Aktifkan
|
||||
</v-btn>
|
||||
</div>
|
||||
</template>
|
||||
</TabelData>
|
||||
</v-card>
|
||||
|
||||
<!-- Pending Patients Table -->
|
||||
<v-card
|
||||
class="pending-table-card mb-4"
|
||||
elevation="2"
|
||||
v-if="pendingPatients.length > 0"
|
||||
>
|
||||
<TabelData
|
||||
:headers="pendingHeaders"
|
||||
:items="pendingPatients"
|
||||
title="INFO PASIEN PENDING"
|
||||
>
|
||||
<template #actions="{ item }">
|
||||
<div class="d-flex gap-1">
|
||||
<v-btn
|
||||
size="small"
|
||||
color="success"
|
||||
variant="flat"
|
||||
@click="processPatient(item, 'proses')"
|
||||
>
|
||||
Proses
|
||||
</v-btn>
|
||||
</div>
|
||||
</template>
|
||||
</TabelData>
|
||||
</v-card>
|
||||
|
||||
<!-- Klinik Dialog -->
|
||||
<v-dialog v-model="showKlinikDialog" max-width="900px">
|
||||
<v-card>
|
||||
<v-card-title class="d-flex align-center pa-4 bg-primary text-white">
|
||||
<v-btn icon @click="showKlinikDialog = false" class="mr-2">
|
||||
<v-icon color="white">mdi-arrow-left</v-icon>
|
||||
</v-btn>
|
||||
<span class="text-h6">Buat Antrean Klinik Ruang</span>
|
||||
</v-card-title>
|
||||
|
||||
<v-card-subtitle class="pa-4 text-center">
|
||||
<h3 class="text-h6 mb-3">Pilih Klinik</h3>
|
||||
<div class="d-flex justify-end gap-2">
|
||||
<v-text-field
|
||||
v-model="klinikSearch"
|
||||
placeholder="Pencarian"
|
||||
density="compact"
|
||||
hide-details
|
||||
style="max-width: 200px"
|
||||
></v-text-field>
|
||||
</div>
|
||||
</v-card-subtitle>
|
||||
|
||||
<v-card-text class="pa-4">
|
||||
<v-row>
|
||||
<v-col
|
||||
v-for="klinik in filteredKliniks"
|
||||
:key="klinik.id"
|
||||
cols="12"
|
||||
sm="6"
|
||||
md="4"
|
||||
>
|
||||
<v-card
|
||||
class="klinik-card"
|
||||
elevation="1"
|
||||
@click="selectKlinik(klinik)"
|
||||
>
|
||||
<v-card-text class="text-center pa-4">
|
||||
<div class="text-h6 font-weight-bold">{{ klinik.name }}</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- Penunjang Dialog -->
|
||||
<v-dialog v-model="showPenunjangDialog" max-width="900px">
|
||||
<v-card>
|
||||
<v-card-title class="d-flex align-center pa-4 bg-secondary text-white">
|
||||
<v-btn icon @click="showPenunjangDialog = false" class="mr-2">
|
||||
<v-icon color="white">mdi-arrow-left</v-icon>
|
||||
</v-btn>
|
||||
<span class="text-h6">Buat Antrean Penunjang</span>
|
||||
</v-card-title>
|
||||
|
||||
<v-card-subtitle class="pa-4">
|
||||
<h3 class="text-h6 mb-3 text-center">Pilih Ruang</h3>
|
||||
<div class="d-flex justify-end gap-2">
|
||||
<v-text-field
|
||||
v-model="penunjangSearch"
|
||||
placeholder="Pencarian"
|
||||
density="compact"
|
||||
hide-details
|
||||
style="max-width: 200px"
|
||||
></v-text-field>
|
||||
</div>
|
||||
</v-card-subtitle>
|
||||
|
||||
<v-card-text class="pa-4">
|
||||
<v-row>
|
||||
<v-col
|
||||
v-for="penunjang in filteredPenunjangs"
|
||||
:key="penunjang.id"
|
||||
cols="12"
|
||||
sm="6"
|
||||
md="4"
|
||||
>
|
||||
<v-card
|
||||
class="penunjang-card"
|
||||
elevation="1"
|
||||
@click="selectPenunjang(penunjang)"
|
||||
>
|
||||
<v-card-text class="text-center pa-4">
|
||||
<div class="text-h6 font-weight-bold">
|
||||
{{ penunjang.name }}
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- Change Klinik Dialog -->
|
||||
<v-dialog v-model="showChangeKlinikDialog" max-width="900px">
|
||||
<v-card>
|
||||
<v-card-title class="d-flex align-center pa-4 bg-info text-white">
|
||||
<v-btn icon @click="showChangeKlinikDialog = false" class="mr-2">
|
||||
<v-icon color="white">mdi-arrow-left</v-icon>
|
||||
</v-btn>
|
||||
<span class="text-h6">Ubah Klinik</span>
|
||||
</v-card-title>
|
||||
|
||||
<v-card-subtitle class="pa-4">
|
||||
<h3 class="text-h6 mb-3 text-center">Pilih Klinik Baru</h3>
|
||||
<div class="d-flex justify-end gap-2">
|
||||
<v-text-field
|
||||
v-model="changeKlinikSearch"
|
||||
placeholder="Pencarian"
|
||||
density="compact"
|
||||
hide-details
|
||||
style="max-width: 200px"
|
||||
></v-text-field>
|
||||
</div>
|
||||
</v-card-subtitle>
|
||||
|
||||
<v-card-text class="pa-4">
|
||||
<v-row>
|
||||
<v-col
|
||||
v-for="klinik in filteredChangeKliniks"
|
||||
:key="klinik.id"
|
||||
cols="12"
|
||||
sm="6"
|
||||
md="4"
|
||||
>
|
||||
<v-card
|
||||
class="klinik-card"
|
||||
elevation="1"
|
||||
@click="changeKlinik(klinik)"
|
||||
>
|
||||
<v-card-text class="text-center pa-4">
|
||||
<div class="text-h6 font-weight-bold">{{ klinik.name }}</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- Snackbar -->
|
||||
<v-snackbar
|
||||
v-model="snackbar"
|
||||
:color="snackbarColor"
|
||||
:timeout="3000"
|
||||
location="top right"
|
||||
>
|
||||
{{ snackbarText }}
|
||||
<template v-slot:actions>
|
||||
<v-btn icon @click="snackbar = false">
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import TabelData from "../components/TabelData.vue";
|
||||
import { useQueue } from "../composables/useQueue";
|
||||
|
||||
// Use queue composable with 'loket' admin type
|
||||
const {
|
||||
snackbar,
|
||||
snackbarText,
|
||||
snackbarColor,
|
||||
showKlinikDialog,
|
||||
showPenunjangDialog,
|
||||
showChangeKlinikDialog,
|
||||
klinikSearch,
|
||||
penunjangSearch,
|
||||
changeKlinikSearch,
|
||||
currentProcessingPatient,
|
||||
diLoketPatients,
|
||||
terlambatPatients,
|
||||
pendingPatients,
|
||||
nextPatient,
|
||||
totalPasien,
|
||||
quotaUsed,
|
||||
filteredKliniks,
|
||||
filteredPenunjangs,
|
||||
filteredChangeKliniks,
|
||||
callNext,
|
||||
callMultiplePatients,
|
||||
processPatient,
|
||||
selectKlinik,
|
||||
selectPenunjang,
|
||||
openPenunjangDialog,
|
||||
changeKlinik,
|
||||
} = useQueue('loket');
|
||||
|
||||
// Headers for different tables
|
||||
const diLoketHeaders = ref([
|
||||
{ title: "No", value: "no", sortable: false, width: "60px" },
|
||||
{ title: "Jam Panggil", value: "jamPanggil", sortable: true, width: "100px" },
|
||||
{ title: "Barcode", value: "barcode", sortable: true, width: "140px" },
|
||||
{ title: "No Antrian", value: "noAntrian", sortable: true, width: "200px" },
|
||||
{ title: "Shift", value: "shift", sortable: true, width: "80px" },
|
||||
{ title: "Klinik", value: "klinik", sortable: true, width: "120px" },
|
||||
{ title: "Fast Track", value: "fastTrack", sortable: true, width: "100px" },
|
||||
{ title: "Pembayaran", value: "pembayaran", sortable: true, width: "100px" },
|
||||
{ title: "Aksi", value: "aksi", sortable: false, width: "200px" },
|
||||
]);
|
||||
|
||||
const terlambatHeaders = ref([
|
||||
{ title: "No", value: "no", sortable: false, width: "60px" },
|
||||
{ title: "Barcode", value: "barcode", sortable: true, width: "140px" },
|
||||
{ title: "No Antrian", value: "noAntrian", sortable: true, width: "200px" },
|
||||
{ title: "Shift", value: "shift", sortable: true, width: "80px" },
|
||||
{ title: "Klinik", value: "klinik", sortable: true, width: "120px" },
|
||||
{ title: "Aksi", value: "aksi", sortable: false, width: "100px" },
|
||||
]);
|
||||
|
||||
const pendingHeaders = ref([
|
||||
{ title: "#", value: "no", sortable: false, width: "60px" },
|
||||
{ title: "Barcode", value: "barcode", sortable: true, width: "140px" },
|
||||
{ title: "No Antrian", value: "noAntrian", sortable: true, width: "200px" },
|
||||
{ title: "Shift", value: "shift", sortable: true, width: "80px" },
|
||||
{ title: "Klinik", value: "klinik", sortable: true, width: "120px" },
|
||||
{ title: "Fast Track", value: "fastTrack", sortable: true, width: "100px" },
|
||||
{ title: "Pembayaran", value: "pembayaran", sortable: true, width: "100px" },
|
||||
{ title: "Aksi", value: "aksi", sortable: false, width: "100px" },
|
||||
]);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.loket-container {
|
||||
background: #f5f7fa;
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
background: linear-gradient(135deg, #1976d2 0%, #1565c0 100%);
|
||||
border-radius: 16px;
|
||||
margin-bottom: 24px;
|
||||
box-shadow: 0 8px 32px rgba(25, 118, 210, 0.3);
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 32px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 16px;
|
||||
padding: 16px;
|
||||
margin-right: 20px;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
margin: 4px 0 0 0;
|
||||
opacity: 0.9;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.current-processing-card,
|
||||
.quota-call-card,
|
||||
.main-table-card,
|
||||
.late-table-card,
|
||||
.pending-table-card {
|
||||
border-radius: 16px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.current-processing-card {
|
||||
background: linear-gradient(135deg, #fff3e0 0%, #ffe0b2 100%);
|
||||
border: 2px solid rgba(255, 152, 0, 0.2);
|
||||
}
|
||||
|
||||
.quota-call-card {
|
||||
background: linear-gradient(135deg, #e3f2fd 0%, #f5f5f5 100%);
|
||||
border: 2px solid rgba(33, 150, 243, 0.2);
|
||||
}
|
||||
|
||||
.patient-info .text-h5 {
|
||||
color: #1976d2;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.quota-label {
|
||||
font-size: 13px;
|
||||
color: #6b7280;
|
||||
font-weight: 500;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.quota-number {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
color: #1f2937;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.quota-available {
|
||||
color: #4caf50;
|
||||
}
|
||||
|
||||
.quota-used-text {
|
||||
font-size: 13px;
|
||||
color: #6b7280;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.call-controls {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.call-label {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.call-buttons {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.call-btn {
|
||||
min-width: 80px;
|
||||
height: 48px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.call-btn-text {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.klinik-card,
|
||||
.penunjang-card {
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.klinik-card:hover,
|
||||
.penunjang-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.v-btn {
|
||||
text-transform: none !important;
|
||||
}
|
||||
|
||||
.v-btn--size-small {
|
||||
height: 32px;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.text-success {
|
||||
color: #4caf50 !important;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.header-content {
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.action-buttons .v-btn {
|
||||
flex: 1;
|
||||
min-width: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.loket-container {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
padding: 24px 20px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.action-buttons .v-btn {
|
||||
width: 100%;
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.quota-number {
|
||||
font-size: 28px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,694 @@
|
||||
<!-- pages/AdminPenunjang.vue -->
|
||||
<template>
|
||||
<div class="loket-container">
|
||||
<!-- Header Section -->
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<div class="header-left">
|
||||
<div class="header-icon">
|
||||
<v-icon size="32" color="white">mdi-clipboard-pulse</v-icon>
|
||||
</div>
|
||||
<div class="header-text">
|
||||
<h1 class="page-title">Penunjang Admin</h1>
|
||||
<p class="page-subtitle">Rabu, 13 Agustus 2025 - Pelayanan</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<v-chip color="success" variant="flat" class="mr-2">
|
||||
Total {{ totalPasien }} Pasien
|
||||
</v-chip>
|
||||
<v-chip color="white" variant="flat" class="text-primary">
|
||||
Max 150 Pasien
|
||||
</v-chip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Combined Control Section -->
|
||||
<v-row class="mb-4">
|
||||
<!-- Left Side: Current Processing -->
|
||||
<v-col cols="12" lg="6">
|
||||
<!-- Current Patient Processing Card -->
|
||||
<v-card class="current-processing-card" elevation="2">
|
||||
<v-card-text class="pa-4">
|
||||
<!-- State when there's a patient being processed -->
|
||||
<div
|
||||
v-if="currentProcessingPatient"
|
||||
class="d-flex align-center justify-space-between"
|
||||
>
|
||||
<div class="patient-info">
|
||||
<v-chip color="primary" variant="flat" class="mb-2">
|
||||
<v-icon start>mdi-account-clock</v-icon>
|
||||
SEDANG DIPROSES
|
||||
</v-chip>
|
||||
<div class="text-h5 font-weight-bold mb-1">
|
||||
{{ currentProcessingPatient.noAntrian.split(" |")[0] }}
|
||||
</div>
|
||||
<div class="text-subtitle-1 text-grey-darken-1">
|
||||
{{ currentProcessingPatient.barcode }} |
|
||||
{{ currentProcessingPatient.klinik }}
|
||||
</div>
|
||||
<div class="patient-meta">
|
||||
Jenis Pembayaran: {{ currentProcessingPatient.pembayaran }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="action-buttons d-flex flex-column">
|
||||
<v-btn
|
||||
color="success"
|
||||
variant="flat"
|
||||
class="mb-2"
|
||||
width="120"
|
||||
@click="processPatient(currentProcessingPatient, 'check-in')"
|
||||
>
|
||||
<v-icon start>mdi-check-circle</v-icon>
|
||||
Check In
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="warning"
|
||||
variant="flat"
|
||||
class="mb-2"
|
||||
width="120"
|
||||
@click="processPatient(currentProcessingPatient, 'terlambat')"
|
||||
>
|
||||
<v-icon start>mdi-clock-alert</v-icon>
|
||||
Terlambat
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="error"
|
||||
variant="flat"
|
||||
class="mb-2"
|
||||
width="120"
|
||||
@click="processPatient(currentProcessingPatient, 'pending')"
|
||||
>
|
||||
<v-icon start>mdi-pause-circle</v-icon>
|
||||
Pending
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="error"
|
||||
variant="flat"
|
||||
width="120"
|
||||
@click="showChangeKlinikDialog = true"
|
||||
>
|
||||
<v-icon start>mdi-swap-horizontal</v-icon>
|
||||
Ubah Klinik
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Empty state when no patient is being processed -->
|
||||
<div v-else class="text-center pa-6">
|
||||
<v-icon size="64" color="grey-lighten-1" class="mb-3">
|
||||
mdi-account-off-outline
|
||||
</v-icon>
|
||||
<div class="text-h6 text-grey-darken-1 mb-2">
|
||||
Tidak Ada Pasien yang Diproses
|
||||
</div>
|
||||
<div class="text-body-2 text-grey">
|
||||
Pilih pasien dari tabel "Di Loket" untuk memproses
|
||||
</div>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<!-- Right Side: Queue Info and Call Controls -->
|
||||
<v-col cols="12" lg="6">
|
||||
<v-card class="quota-call-card" elevation="2">
|
||||
<v-card-text class="pa-4">
|
||||
<div class="d-flex align-center justify-center mb-3">
|
||||
<v-chip color="success" variant="flat" size="large">
|
||||
<v-icon start>mdi-account-arrow-right</v-icon>
|
||||
PANGGIL ANTREAN ANJUNGAN
|
||||
</v-chip>
|
||||
</div>
|
||||
|
||||
<v-row class="mb-3">
|
||||
<v-col cols="6" class="text-center">
|
||||
<div class="quota-label">Kuota</div>
|
||||
<div class="quota-number">150</div>
|
||||
</v-col>
|
||||
<v-col cols="6" class="text-center">
|
||||
<div class="quota-label">Tersedia</div>
|
||||
<div class="quota-number quota-available">
|
||||
{{ 150 - quotaUsed }}
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<div class="text-center mb-3">
|
||||
<div class="quota-used-text">
|
||||
Total Quota Terpakai: {{ quotaUsed }}
|
||||
</div>
|
||||
<v-progress-linear
|
||||
:model-value="(quotaUsed / 150) * 100"
|
||||
color="success"
|
||||
height="6"
|
||||
rounded
|
||||
class="mt-2"
|
||||
></v-progress-linear>
|
||||
</div>
|
||||
|
||||
<v-divider class="my-3"></v-divider>
|
||||
|
||||
<div class="call-controls">
|
||||
<div class="call-label mb-2">Kumulatif Panggil Antrean</div>
|
||||
<div class="call-buttons">
|
||||
<v-btn
|
||||
color="success"
|
||||
variant="flat"
|
||||
class="call-btn"
|
||||
@click="callNext"
|
||||
:disabled="!nextPatient"
|
||||
>
|
||||
<span class="call-btn-text">1</span>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="info"
|
||||
variant="flat"
|
||||
class="call-btn"
|
||||
@click="callMultiplePatients(5)"
|
||||
>
|
||||
<span class="call-btn-text">5</span>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="warning"
|
||||
variant="flat"
|
||||
class="call-btn"
|
||||
@click="callMultiplePatients(10)"
|
||||
>
|
||||
<span class="call-btn-text">10</span>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="error"
|
||||
variant="flat"
|
||||
class="call-btn"
|
||||
@click="callMultiplePatients(20)"
|
||||
>
|
||||
<span class="call-btn-text">20</span>
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row class="mb-4">
|
||||
<v-col cols="12">
|
||||
<div class="d-flex justify-center">
|
||||
<v-btn
|
||||
color="secondary"
|
||||
variant="flat"
|
||||
size="large"
|
||||
@click="showPenunjangDialog = true"
|
||||
class="mx-2"
|
||||
>
|
||||
<v-icon start>mdi-clipboard-pulse</v-icon>
|
||||
Buat Antrean Penunjang
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- Di Loket Patients Table -->
|
||||
<v-card class="main-table-card mb-4" elevation="2">
|
||||
<TabelData
|
||||
:headers="diLoketHeaders"
|
||||
:items="diLoketPatients"
|
||||
title="DATA PASIEN - DI LOKET"
|
||||
>
|
||||
<template #actions="{ item }">
|
||||
<div class="d-flex gap-1">
|
||||
<v-btn
|
||||
size="small"
|
||||
color="info"
|
||||
variant="flat"
|
||||
@click="processPatient(item, 'aktifkan')"
|
||||
>
|
||||
Aktifkan
|
||||
</v-btn>
|
||||
<v-btn
|
||||
size="small"
|
||||
color="success"
|
||||
variant="flat"
|
||||
@click="processPatient(item, 'proses')"
|
||||
>
|
||||
Proses
|
||||
</v-btn>
|
||||
</div>
|
||||
</template>
|
||||
</TabelData>
|
||||
</v-card>
|
||||
|
||||
<!-- Terlambat Patients Table -->
|
||||
<v-card
|
||||
class="late-table-card mb-4"
|
||||
elevation="2"
|
||||
v-if="terlambatPatients.length > 0"
|
||||
>
|
||||
<TabelData
|
||||
:headers="terlambatHeaders"
|
||||
:items="terlambatPatients"
|
||||
title="INFO PASIEN LAPOR TERLAMBAT"
|
||||
>
|
||||
<template #actions="{ item }">
|
||||
<div class="d-flex gap-1">
|
||||
<v-btn
|
||||
size="small"
|
||||
color="success"
|
||||
variant="flat"
|
||||
@click="processPatient(item, 'aktifkan')"
|
||||
>
|
||||
Aktifkan
|
||||
</v-btn>
|
||||
</div>
|
||||
</template>
|
||||
</TabelData>
|
||||
</v-card>
|
||||
|
||||
<!-- Pending Patients Table -->
|
||||
<v-card
|
||||
class="pending-table-card mb-4"
|
||||
elevation="2"
|
||||
v-if="pendingPatients.length > 0"
|
||||
>
|
||||
<TabelData
|
||||
:headers="pendingHeaders"
|
||||
:items="pendingPatients"
|
||||
title="INFO PASIEN PENDING"
|
||||
>
|
||||
<template #actions="{ item }">
|
||||
<div class="d-flex gap-1">
|
||||
<v-btn
|
||||
size="small"
|
||||
color="success"
|
||||
variant="flat"
|
||||
@click="processPatient(item, 'proses')"
|
||||
>
|
||||
Proses
|
||||
</v-btn>
|
||||
</div>
|
||||
</template>
|
||||
</TabelData>
|
||||
</v-card>
|
||||
|
||||
<!-- Penunjang Dialog -->
|
||||
<v-dialog v-model="showPenunjangDialog" max-width="900px">
|
||||
<v-card>
|
||||
<v-card-title class="d-flex align-center pa-4 bg-secondary text-white">
|
||||
<v-btn icon @click="showPenunjangDialog = false" class="mr-2">
|
||||
<v-icon color="white">mdi-arrow-left</v-icon>
|
||||
</v-btn>
|
||||
<span class="text-h6">Buat Antrean Penunjang</span>
|
||||
</v-card-title>
|
||||
|
||||
<v-card-subtitle class="pa-4">
|
||||
<h3 class="text-h6 mb-3 text-center">Pilih Ruang</h3>
|
||||
<div class="d-flex justify-end gap-2">
|
||||
<v-text-field
|
||||
v-model="penunjangSearch"
|
||||
placeholder="Pencarian"
|
||||
density="compact"
|
||||
hide-details
|
||||
style="max-width: 200px"
|
||||
></v-text-field>
|
||||
</div>
|
||||
</v-card-subtitle>
|
||||
|
||||
<v-card-text class="pa-4">
|
||||
<v-row>
|
||||
<v-col
|
||||
v-for="penunjang in filteredPenunjangs"
|
||||
:key="penunjang.id"
|
||||
cols="12"
|
||||
sm="6"
|
||||
md="4"
|
||||
>
|
||||
<v-card
|
||||
class="penunjang-card"
|
||||
elevation="1"
|
||||
@click="selectPenunjang(penunjang)"
|
||||
>
|
||||
<v-card-text class="text-center pa-4">
|
||||
<div class="text-h6 font-weight-bold">
|
||||
{{ penunjang.name }}
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- Change Klinik Dialog -->
|
||||
<v-dialog v-model="showChangeKlinikDialog" max-width="900px">
|
||||
<v-card>
|
||||
<v-card-title class="d-flex align-center pa-4 bg-info text-white">
|
||||
<v-btn icon @click="showChangeKlinikDialog = false" class="mr-2">
|
||||
<v-icon color="white">mdi-arrow-left</v-icon>
|
||||
</v-btn>
|
||||
<span class="text-h6">Ubah Klinik</span>
|
||||
</v-card-title>
|
||||
|
||||
<v-card-subtitle class="pa-4">
|
||||
<h3 class="text-h6 mb-3 text-center">Pilih Klinik Baru</h3>
|
||||
<div class="d-flex justify-end gap-2">
|
||||
<v-text-field
|
||||
v-model="changeKlinikSearch"
|
||||
placeholder="Pencarian"
|
||||
density="compact"
|
||||
hide-details
|
||||
style="max-width: 200px"
|
||||
></v-text-field>
|
||||
</div>
|
||||
</v-card-subtitle>
|
||||
|
||||
<v-card-text class="pa-4">
|
||||
<v-row>
|
||||
<v-col
|
||||
v-for="klinik in filteredChangeKliniks"
|
||||
:key="klinik.id"
|
||||
cols="12"
|
||||
sm="6"
|
||||
md="4"
|
||||
>
|
||||
<v-card
|
||||
class="klinik-card"
|
||||
elevation="1"
|
||||
@click="changeKlinik(klinik)"
|
||||
>
|
||||
<v-card-text class="text-center pa-4">
|
||||
<div class="text-h6 font-weight-bold">{{ klinik.name }}</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- Snackbar -->
|
||||
<v-snackbar
|
||||
v-model="snackbar"
|
||||
:color="snackbarColor"
|
||||
:timeout="3000"
|
||||
location="top right"
|
||||
>
|
||||
{{ snackbarText }}
|
||||
<template v-slot:actions>
|
||||
<v-btn icon @click="snackbar = false">
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import TabelData from "../components/TabelData.vue";
|
||||
import { useQueue } from "../composables/useQueue";
|
||||
|
||||
// Use queue composable with 'penunjang' admin type
|
||||
const {
|
||||
snackbar,
|
||||
snackbarText,
|
||||
snackbarColor,
|
||||
showPenunjangDialog,
|
||||
showChangeKlinikDialog,
|
||||
penunjangSearch,
|
||||
changeKlinikSearch,
|
||||
currentProcessingPatient,
|
||||
diLoketPatients,
|
||||
terlambatPatients,
|
||||
pendingPatients,
|
||||
nextPatient,
|
||||
totalPasien,
|
||||
quotaUsed,
|
||||
filteredPenunjangs,
|
||||
filteredChangeKliniks,
|
||||
callNext,
|
||||
callMultiplePatients,
|
||||
processPatient,
|
||||
selectPenunjang,
|
||||
changeKlinik,
|
||||
} = useQueue('penunjang');
|
||||
|
||||
const diLoketHeaders = ref([
|
||||
{ title: "No", value: "no", sortable: false, width: "60px" },
|
||||
{ title: "Jam Panggil", value: "jamPanggil", sortable: true, width: "100px" },
|
||||
{ title: "Barcode", value: "barcode", sortable: true, width: "140px" },
|
||||
{ title: "No Antrian", value: "noAntrian", sortable: true, width: "200px" },
|
||||
{ title: "Shift", value: "shift", sortable: true, width: "80px" },
|
||||
{ title: "Klinik", value: "klinik", sortable: true, width: "120px" },
|
||||
{ title: "Fast Track", value: "fastTrack", sortable: true, width: "100px" },
|
||||
{ title: "Pembayaran", value: "pembayaran", sortable: true, width: "100px" },
|
||||
{ title: "Aksi", value: "aksi", sortable: false, width: "200px" },
|
||||
]);
|
||||
|
||||
const terlambatHeaders = ref([
|
||||
{ title: "No", value: "no", sortable: false, width: "60px" },
|
||||
{ title: "Barcode", value: "barcode", sortable: true, width: "140px" },
|
||||
{ title: "No Antrian", value: "noAntrian", sortable: true, width: "200px" },
|
||||
{ title: "Shift", value: "shift", sortable: true, width: "80px" },
|
||||
{ title: "Klinik", value: "klinik", sortable: true, width: "120px" },
|
||||
{ title: "Aksi", value: "aksi", sortable: false, width: "100px" },
|
||||
]);
|
||||
|
||||
const pendingHeaders = ref([
|
||||
{ title: "#", value: "no", sortable: false, width: "60px" },
|
||||
{ title: "Barcode", value: "barcode", sortable: true, width: "140px" },
|
||||
{ title: "No Antrian", value: "noAntrian", sortable: true, width: "200px" },
|
||||
{ title: "Shift", value: "shift", sortable: true, width: "80px" },
|
||||
{ title: "Klinik", value: "klinik", sortable: true, width: "120px" },
|
||||
{ title: "Fast Track", value: "fastTrack", sortable: true, width: "100px" },
|
||||
{ title: "Pembayaran", value: "pembayaran", sortable: true, width: "100px" },
|
||||
{ title: "Aksi", value: "aksi", sortable: false, width: "100px" },
|
||||
]);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.loket-container {
|
||||
background: #f5f7fa;
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
background: linear-gradient(135deg, #9c27b0 0%, #7b1fa2 100%);
|
||||
border-radius: 16px;
|
||||
margin-bottom: 24px;
|
||||
box-shadow: 0 8px 32px rgba(156, 39, 176, 0.3);
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 32px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 16px;
|
||||
padding: 16px;
|
||||
margin-right: 20px;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
margin: 4px 0 0 0;
|
||||
opacity: 0.9;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.current-processing-card,
|
||||
.quota-call-card,
|
||||
.main-table-card,
|
||||
.late-table-card,
|
||||
.pending-table-card {
|
||||
border-radius: 16px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.current-processing-card {
|
||||
background: linear-gradient(135deg, #fff3e0 0%, #ffe0b2 100%);
|
||||
border: 2px solid rgba(255, 152, 0, 0.2);
|
||||
}
|
||||
|
||||
.quota-call-card {
|
||||
background: linear-gradient(135deg, #e3f2fd 0%, #f5f5f5 100%);
|
||||
border: 2px solid rgba(33, 150, 243, 0.2);
|
||||
}
|
||||
|
||||
.patient-info .text-h5 {
|
||||
color: #1976d2;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.quota-label {
|
||||
font-size: 13px;
|
||||
color: #6b7280;
|
||||
font-weight: 500;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.quota-number {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
color: #1f2937;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.quota-available {
|
||||
color: #4caf50;
|
||||
}
|
||||
|
||||
.quota-used-text {
|
||||
font-size: 13px;
|
||||
color: #6b7280;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.call-controls {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.call-label {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.call-buttons {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.call-btn {
|
||||
min-width: 80px;
|
||||
height: 48px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.call-btn-text {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.v-btn {
|
||||
text-transform: none !important;
|
||||
}
|
||||
|
||||
.v-btn--size-small {
|
||||
height: 32px;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.text-success {
|
||||
color: #4caf50 !important;
|
||||
}
|
||||
|
||||
.penunjang-card {
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.penunjang-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.klinik-card {
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.klinik-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.header-content {
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.action-buttons .v-btn {
|
||||
flex: 1;
|
||||
min-width: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.loket-container {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
padding: 24px 20px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.action-buttons .v-btn {
|
||||
width: 100%;
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.quota-number {
|
||||
font-size: 28px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+312
-335
@@ -1,7 +1,5 @@
|
||||
<!-- pages/Anjungan.vue -->
|
||||
<template>
|
||||
<div class="anjungan-container">
|
||||
<!-- Header Section -->
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<div class="header-left">
|
||||
@@ -22,60 +20,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Controls Section -->
|
||||
<!-- <v-card class="controls-card mb-4" elevation="2">
|
||||
<v-card-text class="py-3">
|
||||
<v-row align="center">
|
||||
<v-col cols="12" md="6">
|
||||
<div class="d-flex align-center flex-wrap gap-3">
|
||||
<v-select
|
||||
v-model="selectedStatus"
|
||||
:items="statusOptions"
|
||||
label="Filter Status"
|
||||
density="compact"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
clearable
|
||||
style="min-width: 160px;"
|
||||
/>
|
||||
|
||||
<v-text-field
|
||||
v-model="searchQuery"
|
||||
label="Cari Klinik"
|
||||
density="compact"
|
||||
variant="outlined"
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
hide-details
|
||||
clearable
|
||||
style="min-width: 200px;"
|
||||
/>
|
||||
</div>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<div class="d-flex justify-end align-center flex-wrap gap-2">
|
||||
<v-btn
|
||||
variant="outlined"
|
||||
prepend-icon="mdi-refresh"
|
||||
@click="refreshData"
|
||||
:loading="loading"
|
||||
size="small"
|
||||
>
|
||||
Refresh
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card> -->
|
||||
|
||||
<!-- Clinic Cards -->
|
||||
<v-card elevation="2" class="main-content-card">
|
||||
<!-- <v-card-title class="d-flex align-center pa-4 bg-grey-lighten-4">
|
||||
<v-icon class="mr-2">mdi-hospital-marker</v-icon>
|
||||
<span>Daftar Klinik - {{ filteredClinics.length }} dari {{ totalClinics }} klinik</span>
|
||||
</v-card-title> -->
|
||||
|
||||
<v-card-text class="pa-6">
|
||||
<v-row>
|
||||
<v-col
|
||||
@@ -87,7 +32,7 @@
|
||||
lg="3"
|
||||
class="pa-2"
|
||||
>
|
||||
<v-card
|
||||
<v-card
|
||||
:class="{
|
||||
'clinic-card': true,
|
||||
'clinic-available': clinic.available,
|
||||
@@ -98,19 +43,25 @@
|
||||
elevation="2"
|
||||
>
|
||||
<v-card-text class="text-center pa-4">
|
||||
<!-- Status Chip -->
|
||||
<div class="mb-3">
|
||||
<div class="mb-6">
|
||||
<v-chip
|
||||
:color="clinic.available ? 'success' : 'error'"
|
||||
size="small"
|
||||
variant="flat"
|
||||
class="mr-2"
|
||||
>
|
||||
{{ clinic.available ? 'TERSEDIA' : 'TUTUP' }}
|
||||
</v-chip>
|
||||
<v-chip
|
||||
size="small"
|
||||
:color="clinic.available ? 'info' : 'error'"
|
||||
variant="outlined"
|
||||
>
|
||||
{{ clinic.shift }}
|
||||
</v-chip>
|
||||
</div>
|
||||
|
||||
<!-- Icon -->
|
||||
<div class="clinic-icon-wrapper mb-3">
|
||||
<div class="clinic-icon-wrapper mb-6">
|
||||
<v-icon
|
||||
:icon="clinic.icon"
|
||||
size="40"
|
||||
@@ -118,60 +69,24 @@
|
||||
></v-icon>
|
||||
</div>
|
||||
|
||||
<!-- Clinic Name -->
|
||||
<h3 class="text-h6 font-weight-bold mb-2">
|
||||
{{ clinic.name }}
|
||||
</h3>
|
||||
|
||||
<!-- Subtitle -->
|
||||
<p v-if="clinic.subtitle" class="text-caption text-grey-darken-1 mb-2">
|
||||
{{ clinic.subtitle }}
|
||||
</p>
|
||||
|
||||
<!-- Shift Info -->
|
||||
<div class="shift-info">
|
||||
<v-chip
|
||||
size="small"
|
||||
:color="clinic.available ? 'info' : 'error'"
|
||||
variant="outlined"
|
||||
class="mb-2"
|
||||
>
|
||||
{{ clinic.shift }}
|
||||
</v-chip>
|
||||
<br>
|
||||
<span v-if="clinic.schedule" class="text-caption text-grey-darken-1">
|
||||
{{ clinic.schedule }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Action Button -->
|
||||
<div class="mt-3">
|
||||
<v-btn
|
||||
v-if="clinic.available"
|
||||
color="success"
|
||||
variant="flat"
|
||||
size="small"
|
||||
block
|
||||
>
|
||||
Pilih Klinik
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-else
|
||||
color="error"
|
||||
variant="outlined"
|
||||
size="small"
|
||||
disabled
|
||||
block
|
||||
>
|
||||
Tidak Tersedia
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- Empty State -->
|
||||
<div v-if="filteredClinics.length === 0" class="text-center py-8">
|
||||
<v-icon size="64" color="grey-lighten-1">mdi-hospital-marker-outline</v-icon>
|
||||
<h3 class="text-h6 mt-4 text-grey-darken-1">Tidak ada klinik yang sesuai filter</h3>
|
||||
@@ -180,9 +95,8 @@
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<!-- Selection Dialog -->
|
||||
<v-dialog v-model="showDialog" max-width="500">
|
||||
<v-card>
|
||||
<v-dialog v-model="showVisitTypeDialog" max-width="400" persistent>
|
||||
<v-card class="text-center" :style="{ borderRadius: '16px' }">
|
||||
<v-card-title class="d-flex align-center bg-primary text-white">
|
||||
<v-icon class="mr-2">mdi-check-circle</v-icon>
|
||||
Konfirmasi Pilihan
|
||||
@@ -207,24 +121,191 @@
|
||||
</div>
|
||||
</div>
|
||||
</v-card-text>
|
||||
|
||||
<v-card-actions class="pa-4">
|
||||
<v-spacer />
|
||||
<v-btn @click="showDialog = false" variant="text">
|
||||
Batal
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="primary"
|
||||
@click="proceedToRegistration"
|
||||
variant="flat"
|
||||
<v-divider />
|
||||
<v-card-text class="pa-4">
|
||||
<v-btn
|
||||
color="primary"
|
||||
class="mb-3"
|
||||
size="large"
|
||||
block
|
||||
@click="selectVisitType('SEKARANG')"
|
||||
>
|
||||
Lanjutkan
|
||||
SEKARANG
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="warning"
|
||||
class="mb-3"
|
||||
size="large"
|
||||
block
|
||||
@click="selectVisitType('PESAN')"
|
||||
>
|
||||
PESAN
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="error"
|
||||
size="large"
|
||||
block
|
||||
@click="selectVisitType('FAST TRACK')"
|
||||
>
|
||||
FAST TRACK
|
||||
</v-btn>
|
||||
</v-card-text>
|
||||
<v-card-actions class="justify-center pa-4 pt-0">
|
||||
<v-btn variant="outlined" color="grey-darken-1" @click="showVisitTypeDialog = false">
|
||||
Tutup
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<v-dialog v-model="showPaymentTypeDialog" max-width="400" persistent>
|
||||
<v-card class="text-center" :style="{ borderRadius: '16px' }">
|
||||
<v-card-title class="headline pa-4">Jenis Pembayaran</v-card-title>
|
||||
<v-divider />
|
||||
<v-card-text class="pa-4">
|
||||
<v-btn
|
||||
color="success"
|
||||
class="mb-3 text-h6"
|
||||
size="x-large"
|
||||
block
|
||||
@click="selectPaymentType('BPJS')"
|
||||
:style="{ height: '80px', fontWeight: 'bold' }"
|
||||
>
|
||||
BPJS
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="blue-grey-darken-2"
|
||||
class="text-h6"
|
||||
size="x-large"
|
||||
block
|
||||
@click="selectPaymentType('UMUM')"
|
||||
:style="{ height: '80px', fontWeight: 'bold' }"
|
||||
>
|
||||
UMUM / JKMM / SPM / DLL
|
||||
</v-btn>
|
||||
</v-card-text>
|
||||
<v-card-actions class="justify-center pa-4 pt-0">
|
||||
<v-btn variant="outlined" color="grey-darken-1" @click="showPaymentTypeDialog = false">
|
||||
Tutup
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<v-dialog v-model="showBookingFormDialog" max-width="500" persistent>
|
||||
<v-card :style="{ borderRadius: '16px' }">
|
||||
<v-card-title class="headline pa-4 bg-primary text-white">
|
||||
Pemesanan Antrian Poliklinik
|
||||
</v-card-title>
|
||||
<v-card-text class="pa-6">
|
||||
<v-form @submit.prevent="submitBooking">
|
||||
<v-text-field
|
||||
v-model="bookingForm.date"
|
||||
label="Tanggal"
|
||||
type="date"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
required
|
||||
class="mb-3"
|
||||
></v-text-field>
|
||||
|
||||
<v-select
|
||||
v-model="bookingForm.shift"
|
||||
:items="['Shift 1', 'Shift 2']"
|
||||
label="Shift"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
required
|
||||
class="mb-3"
|
||||
></v-select>
|
||||
|
||||
<v-select
|
||||
v-model="bookingForm.payment"
|
||||
:items="['BPJS', 'UMUM/JKMM/SPM/DLL']"
|
||||
label="Pembayaran"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
required
|
||||
></v-select>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
<v-card-actions class="pa-4">
|
||||
<v-spacer />
|
||||
<v-btn @click="showBookingFormDialog = false" variant="outlined" color="grey-darken-1">
|
||||
Tutup
|
||||
</v-btn>
|
||||
<v-btn color="success" variant="flat" @click="submitBooking">
|
||||
Simpan
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<v-dialog v-model="showFastTrackFormDialog" max-width="500" persistent>
|
||||
<v-card :style="{ borderRadius: '16px' }">
|
||||
<v-card-title class="headline pa-4 bg-primary text-white">
|
||||
Form Fast Track
|
||||
</v-card-title>
|
||||
<v-card-text class="pa-6">
|
||||
<v-form @submit.prevent="submitFastTrack">
|
||||
<v-text-field
|
||||
v-model="fastTrackForm.guarantor"
|
||||
label="Penanggung Jawab"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
required
|
||||
class="mb-3"
|
||||
></v-text-field>
|
||||
|
||||
<v-text-field
|
||||
v-model="fastTrackForm.noRek"
|
||||
label="No Rekam Medis (Jika Ada)"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
class="mb-3"
|
||||
></v-text-field>
|
||||
|
||||
<v-text-field
|
||||
v-model="fastTrackForm.patientName"
|
||||
label="Nama Pasien"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
required
|
||||
class="mb-3"
|
||||
></v-text-field>
|
||||
|
||||
<v-textarea
|
||||
v-model="fastTrackForm.reason"
|
||||
label="Alasan Fast Track"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
required
|
||||
rows="3"
|
||||
class="mb-3"
|
||||
></v-textarea>
|
||||
|
||||
<v-select
|
||||
v-model="fastTrackForm.payment"
|
||||
:items="['BPJS', 'UMUM/JKMM/SPM/DLL']"
|
||||
label="Pembayaran"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
required
|
||||
></v-select>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
<v-card-actions class="pa-4">
|
||||
<v-spacer />
|
||||
<v-btn @click="showFastTrackFormDialog = false" variant="outlined" color="grey-darken-1">
|
||||
Tutup
|
||||
</v-btn>
|
||||
<v-btn color="success" variant="flat" @click="submitFastTrack">
|
||||
Simpan
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- Snackbar -->
|
||||
<v-snackbar
|
||||
v-model="snackbar"
|
||||
:color="snackbarColor"
|
||||
@@ -232,7 +313,6 @@
|
||||
location="top right"
|
||||
>
|
||||
{{ snackbarText }}
|
||||
|
||||
<template v-slot:actions>
|
||||
<v-btn icon @click="snackbar = false">
|
||||
<v-icon>mdi-close</v-icon>
|
||||
@@ -244,235 +324,72 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
definePageMeta({
|
||||
layout: false, // Disables the layout for this specific page
|
||||
});
|
||||
|
||||
// Reactive data
|
||||
definePageMeta({
|
||||
layout: false, // Disables the layout for this specific page
|
||||
})
|
||||
|
||||
// Reactive data (tambahan untuk dialog baru)
|
||||
const loading = ref(false)
|
||||
const selectedStatus = ref(null)
|
||||
const searchQuery = ref('')
|
||||
const showDialog = ref(false)
|
||||
const showVisitTypeDialog = ref(false) // Dialog: Pilih Kunjungan
|
||||
const showPaymentTypeDialog = ref(false) // Dialog: Jenis Pembayaran (Sekarang)
|
||||
const showBookingFormDialog = ref(false) // Dialog: Form Pesan
|
||||
const showFastTrackFormDialog = ref(false) // Dialog: Form Fast Track
|
||||
const selectedClinic = ref(null)
|
||||
const selectedVisitType = ref(null)
|
||||
const snackbar = ref(false)
|
||||
const snackbarText = ref('')
|
||||
const snackbarColor = ref('success')
|
||||
|
||||
// Options
|
||||
// Form data models
|
||||
const bookingForm = ref({
|
||||
date: new Date().toISOString().substring(0, 10),
|
||||
shift: 'Shift 1',
|
||||
payment: null,
|
||||
})
|
||||
const fastTrackForm = ref({
|
||||
guarantor: '',
|
||||
noRek: '',
|
||||
patientName: '',
|
||||
reason: '',
|
||||
payment: null,
|
||||
})
|
||||
|
||||
|
||||
// Options (Tidak Berubah)
|
||||
const statusOptions = ['Tersedia', 'Tutup']
|
||||
|
||||
// Clinic data
|
||||
// Clinic data (Tidak Berubah)
|
||||
const clinics = ref([
|
||||
{
|
||||
id: 1,
|
||||
name: 'ANAK',
|
||||
subtitle: '',
|
||||
icon: 'mdi-baby-face',
|
||||
shift: 'SHIFT 1',
|
||||
schedule: 'Mulai Pukul 07:00',
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'ANESTESI',
|
||||
subtitle: '',
|
||||
icon: 'mdi-sleep',
|
||||
shift: 'SHIFT 1',
|
||||
schedule: 'Mulai Pukul 07:00',
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'BEDAH',
|
||||
subtitle: '',
|
||||
icon: 'mdi-medical-bag',
|
||||
shift: 'SHIFT 1',
|
||||
schedule: 'Mulai Pukul 07:00',
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'GERIATRI',
|
||||
subtitle: '',
|
||||
icon: 'mdi-account-supervisor',
|
||||
shift: 'SHIFT 1',
|
||||
schedule: 'Mulai Pukul 07:00',
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: 'GIGI DAN MULUT',
|
||||
subtitle: 'GIGI DAN MULUT',
|
||||
icon: 'mdi-tooth',
|
||||
shift: 'SHIFT 1',
|
||||
schedule: 'Mulai Pukul 07:00',
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: 'GIZI',
|
||||
subtitle: '',
|
||||
icon: 'mdi-food-apple',
|
||||
shift: 'SHIFT 1',
|
||||
schedule: 'Mulai Pukul 07:00',
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
name: 'HOM',
|
||||
subtitle: 'HEMATO ONKOLOGI MEDIS',
|
||||
icon: 'mdi-water',
|
||||
shift: 'TUTUP',
|
||||
schedule: '',
|
||||
available: false,
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
name: 'IPD',
|
||||
subtitle: 'PENYAKIT DALAM',
|
||||
icon: 'mdi-hospital',
|
||||
shift: 'SHIFT 1',
|
||||
schedule: 'Mulai Pukul 07:00',
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
name: 'JANTUNG',
|
||||
subtitle: 'CARDIOLOGI',
|
||||
icon: 'mdi-heart-pulse',
|
||||
shift: 'SHIFT 1',
|
||||
schedule: 'Mulai Pukul 07:00',
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
name: 'JIWA',
|
||||
subtitle: '',
|
||||
icon: 'mdi-brain',
|
||||
shift: 'SHIFT 1',
|
||||
schedule: 'Mulai Pukul 07:00',
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
name: 'KANDUNGAN',
|
||||
subtitle: '',
|
||||
icon: 'mdi-human-pregnant',
|
||||
shift: 'SHIFT 1',
|
||||
schedule: 'Mulai Pukul 07:00',
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
name: 'KEMOTERAPI',
|
||||
subtitle: '',
|
||||
icon: 'mdi-needle',
|
||||
shift: 'SHIFT 1',
|
||||
schedule: 'Mulai Pukul 07:00',
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
id: 13,
|
||||
name: 'KOMPLEMENTER',
|
||||
subtitle: 'NYERI',
|
||||
icon: 'mdi-leaf',
|
||||
shift: 'SHIFT 1',
|
||||
schedule: 'Mulai Pukul 07:00',
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
id: 14,
|
||||
name: 'KUL KEL',
|
||||
subtitle: 'KULIT KELAMIN',
|
||||
icon: 'mdi-hand-back-right',
|
||||
shift: 'SHIFT 1',
|
||||
schedule: 'Mulai Pukul 07:00',
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
id: 15,
|
||||
name: 'MATA',
|
||||
subtitle: '',
|
||||
icon: 'mdi-eye',
|
||||
shift: 'SHIFT 1',
|
||||
schedule: 'Mulai Pukul 07:00',
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
id: 16,
|
||||
name: 'MCU',
|
||||
subtitle: '',
|
||||
icon: 'mdi-clipboard-check',
|
||||
shift: 'SHIFT 1',
|
||||
schedule: 'Mulai Pukul 07:00',
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
id: 17,
|
||||
name: 'ONKOLOGI',
|
||||
subtitle: '',
|
||||
icon: 'mdi-ribbon',
|
||||
shift: 'SHIFT 1',
|
||||
schedule: 'Mulai Pukul 07:00',
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
id: 18,
|
||||
name: 'PARU',
|
||||
subtitle: '',
|
||||
icon: 'mdi-lungs',
|
||||
shift: 'SHIFT 1',
|
||||
schedule: 'Mulai Pukul 07:00',
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
id: 19,
|
||||
name: 'R. TINDAKAN',
|
||||
subtitle: 'EMG, ECG, DLL',
|
||||
icon: 'mdi-monitor-heart-rate',
|
||||
shift: 'SHIFT 1',
|
||||
schedule: 'Mulai Pukul 07:00',
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
id: 20,
|
||||
name: 'RADIOTERAPI',
|
||||
subtitle: '',
|
||||
icon: 'mdi-radioactive',
|
||||
shift: 'SHIFT 1',
|
||||
schedule: 'Mulai Pukul 07:00',
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
id: 21,
|
||||
name: 'REHAB MEDIK',
|
||||
subtitle: '',
|
||||
icon: 'mdi-human-cane',
|
||||
shift: 'SHIFT 1',
|
||||
schedule: 'Mulai Pukul 07:00',
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
id: 22,
|
||||
name: 'SARAF',
|
||||
subtitle: 'NEUROLOGI',
|
||||
icon: 'mdi-head-cog',
|
||||
shift: 'SHIFT 1',
|
||||
schedule: 'Mulai Pukul 07:00',
|
||||
available: false,
|
||||
},
|
||||
{
|
||||
id: 23,
|
||||
name: 'THT',
|
||||
subtitle: '',
|
||||
icon: 'mdi-ear-hearing',
|
||||
shift: 'SHIFT 1',
|
||||
schedule: 'Mulai Pukul 07:00',
|
||||
available: true,
|
||||
}
|
||||
// ... data klinik ... (dibiarkan seperti semula)
|
||||
{ id: 1, name: 'ANAK', subtitle: '', icon: 'mdi-baby-face', shift: 'SHIFT 1', schedule: 'Mulai Pukul 07:00', available: true, },
|
||||
{ id: 2, name: 'ANESTESI', subtitle: '', icon: 'mdi-sleep', shift: 'SHIFT 1', schedule: 'Mulai Pukul 07:00', available: true, },
|
||||
{ id: 3, name: 'BEDAH', subtitle: '', icon: 'mdi-medical-bag', shift: 'SHIFT 1', schedule: 'Mulai Pukul 07:00', available: true, },
|
||||
{ id: 4, name: 'GERIATRI', subtitle: '', icon: 'mdi-account-supervisor', shift: 'SHIFT 1', schedule: 'Mulai Pukul 07:00', available: true, },
|
||||
{ id: 5, name: 'GIGI DAN MULUT', subtitle: '', icon: 'mdi-tooth', shift: 'SHIFT 1', schedule: 'Mulai Pukul 07:00', available: true, },
|
||||
{ id: 6, name: 'GIZI', subtitle: '', icon: 'mdi-food-apple', shift: 'SHIFT 1', schedule: 'Mulai Pukul 07:00', available: true, },
|
||||
{ id: 7, name: 'HEMATO ONKOLOGI MEDIS', subtitle: '', icon: 'mdi-water', shift: 'TUTUP', schedule: '', available: false, },
|
||||
{ id: 8, name: 'IPD (PENYAKIT DALAM)', subtitle: '', icon: 'mdi-hospital', shift: 'SHIFT 1', schedule: 'Mulai Pukul 07:00', available: true, },
|
||||
{ id: 9, name: 'JANTUNG (CARDIOLOGI)', subtitle: '', icon: 'mdi-heart-pulse', shift: 'SHIFT 1', schedule: 'Mulai Pukul 07:00', available: true, },
|
||||
{ id: 10, name: 'JIWA', subtitle: '', icon: 'mdi-brain', shift: 'SHIFT 1', schedule: 'Mulai Pukul 07:00', available: true, },
|
||||
{ id: 11, name: 'KANDUNGAN', subtitle: '', icon: 'mdi-human-pregnant', shift: 'SHIFT 1', schedule: 'Mulai Pukul 07:00', available: true, },
|
||||
{ id: 12, name: 'KEMOTERAPI', subtitle: '', icon: 'mdi-needle', shift: 'SHIFT 1', schedule: 'Mulai Pukul 07:00', available: true, },
|
||||
{ id: 13, name: 'KOMPLEMENTER (NYERI)', subtitle: '', icon: 'mdi-leaf', shift: 'SHIFT 1', schedule: 'Mulai Pukul 07:00', available: true, },
|
||||
{ id: 14, name: 'KULIT KELAMIN', subtitle: '', icon: 'mdi-hand-back-right', shift: 'SHIFT 1', schedule: 'Mulai Pukul 07:00', available: true, },
|
||||
{ id: 15, name: 'MATA', subtitle: '', icon: 'mdi-eye', shift: 'SHIFT 1', schedule: 'Mulai Pukul 07:00', available: true, },
|
||||
{ id: 16, name: 'MCU', subtitle: '', icon: 'mdi-clipboard-check', shift: 'SHIFT 1', schedule: 'Mulai Pukul 07:00', available: true, },
|
||||
{ id: 17, name: 'ONKOLOGI', subtitle: '', icon: 'mdi-ribbon', shift: 'SHIFT 1', schedule: 'Mulai Pukul 07:00', available: true, },
|
||||
{ id: 18, name: 'PARU', subtitle: '', icon: 'mdi-lungs', shift: 'SHIFT 1', schedule: 'Mulai Pukul 07:00', available: true, },
|
||||
{ id: 19, name: 'R. TINDAKAN (EMG, ECG, DLL)', subtitle: '', icon: 'mdi-monitor-heart-rate', shift: 'SHIFT 1', schedule: 'Mulai Pukul 07:00', available: true, },
|
||||
{ id: 20, name: 'RADIOTERAPI', subtitle: '', icon: 'mdi-radioactive', shift: 'SHIFT 1', schedule: 'Mulai Pukul 07:00', available: true, },
|
||||
{ id: 21, name: 'REHAB MEDIK', subtitle: '', icon: 'mdi-human-cane', shift: 'SHIFT 1', schedule: 'Mulai Pukul 07:00', available: true, },
|
||||
{ id: 22, name: 'SARAF (NEUROLOGI)', subtitle: '', icon: 'mdi-head-cog', shift: 'SHIFT 1', schedule: 'Mulai Pukul 07:00', available: false, },
|
||||
{ id: 23, name: 'THT', subtitle: '', icon: 'mdi-ear-hearing', shift: 'SHIFT 1', schedule: 'Mulai Pukul 07:00', available: true, }
|
||||
])
|
||||
|
||||
// Computed properties
|
||||
// Computed properties (Tidak Berubah)
|
||||
const filteredClinics = computed(() => {
|
||||
let filtered = clinics.value
|
||||
|
||||
@@ -483,7 +400,7 @@ const filteredClinics = computed(() => {
|
||||
|
||||
if (searchQuery.value) {
|
||||
const query = searchQuery.value.toLowerCase()
|
||||
filtered = filtered.filter(clinic =>
|
||||
filtered = filtered.filter(clinic =>
|
||||
clinic.name.toLowerCase().includes(query) ||
|
||||
clinic.subtitle.toLowerCase().includes(query)
|
||||
)
|
||||
@@ -501,17 +418,76 @@ const showSnackbar = (text, color = 'success') => {
|
||||
snackbar.value = true
|
||||
}
|
||||
|
||||
// 1. Memilih Klinik
|
||||
const selectClinic = (clinic) => {
|
||||
if (clinic.available) {
|
||||
selectedClinic.value = clinic
|
||||
showDialog.value = true
|
||||
showVisitTypeDialog.value = true // Tampilkan dialog Pilih Kunjungan
|
||||
}
|
||||
}
|
||||
|
||||
const proceedToRegistration = () => {
|
||||
showSnackbar(`Mengarahkan ke pendaftaran ${selectedClinic.value.name}...`, 'success')
|
||||
console.log('Proceeding to registration for:', selectedClinic.value.name)
|
||||
showDialog.value = false
|
||||
// 2. Memilih Jenis Kunjungan (Sekarang, Pesan, Fast Track)
|
||||
const selectVisitType = (type) => {
|
||||
selectedVisitType.value = type
|
||||
showVisitTypeDialog.value = false // Tutup dialog jenis kunjungan
|
||||
|
||||
if (type === 'SEKARANG') {
|
||||
showPaymentTypeDialog.value = true // Lanjut ke dialog Jenis Pembayaran
|
||||
} else if (type === 'PESAN') {
|
||||
// Reset form booking sebelum ditampilkan
|
||||
bookingForm.value = {
|
||||
date: new Date().toISOString().substring(0, 10),
|
||||
shift: 'Shift 1',
|
||||
payment: null,
|
||||
}
|
||||
showBookingFormDialog.value = true // Tampilkan form Pesan
|
||||
} else if (type === 'FAST TRACK') {
|
||||
// Reset form fast track sebelum ditampilkan
|
||||
fastTrackForm.value = {
|
||||
guarantor: '',
|
||||
noRek: '',
|
||||
patientName: '',
|
||||
reason: '',
|
||||
payment: null,
|
||||
}
|
||||
showFastTrackFormDialog.value = true // Tampilkan form Fast Track
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Memilih Jenis Pembayaran (untuk SEKARANG)
|
||||
const selectPaymentType = (paymentType) => {
|
||||
showPaymentTypeDialog.value = false
|
||||
const message = `Pendaftaran **${selectedClinic.value.name}** untuk kunjungan **SEKARANG** dengan pembayaran **${paymentType}** berhasil diproses.`
|
||||
showSnackbar(message, 'success')
|
||||
console.log('Proceeding to registration for:', selectedClinic.value.name, 'Type: SEKARANG', 'Payment:', paymentType)
|
||||
selectedClinic.value = null; // Reset
|
||||
}
|
||||
|
||||
// 4. Submit Form Pesan
|
||||
const submitBooking = () => {
|
||||
if (!bookingForm.value.date || !bookingForm.value.shift || !bookingForm.value.payment) {
|
||||
showSnackbar('Mohon lengkapi semua field Pemesanan.', 'error')
|
||||
return
|
||||
}
|
||||
|
||||
showBookingFormDialog.value = false
|
||||
const message = `Pemesanan antrian **${selectedClinic.value.name}** pada ${bookingForm.value.date} (**${bookingForm.value.payment}**) berhasil disimpan.`
|
||||
showSnackbar(message, 'success')
|
||||
console.log('Submitted booking for:', selectedClinic.value.name, bookingForm.value)
|
||||
selectedClinic.value = null; // Reset
|
||||
}
|
||||
|
||||
// 5. Submit Form Fast Track
|
||||
const submitFastTrack = () => {
|
||||
if (!fastTrackForm.value.guarantor || !fastTrackForm.value.patientName || !fastTrackForm.value.reason || !fastTrackForm.value.payment) {
|
||||
showSnackbar('Mohon lengkapi semua field Fast Track yang wajib diisi.', 'error')
|
||||
return
|
||||
}
|
||||
showFastTrackFormDialog.value = false
|
||||
const message = `Permintaan Fast Track **${selectedClinic.value.name}** atas nama ${fastTrackForm.value.patientName} (**${fastTrackForm.value.payment}**) telah diajukan.`
|
||||
showSnackbar(message, 'success')
|
||||
console.log('Submitted Fast Track for:', selectedClinic.value.name, fastTrackForm.value)
|
||||
selectedClinic.value = null; // Reset
|
||||
}
|
||||
|
||||
const refreshData = () => {
|
||||
@@ -529,6 +505,7 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* Style yang sudah ada (Tidak Berubah) */
|
||||
.anjungan-container {
|
||||
background: #f5f7fa;
|
||||
min-height: 100vh;
|
||||
@@ -595,7 +572,7 @@ onMounted(() => {
|
||||
.clinic-card {
|
||||
cursor: pointer;
|
||||
border-radius: 16px !important;
|
||||
height: 280px;
|
||||
height: 240px;
|
||||
background: white;
|
||||
}
|
||||
|
||||
@@ -629,7 +606,7 @@ onMounted(() => {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
/* Responsive Design */
|
||||
/* Responsive Design (Tidak Berubah) */
|
||||
@media (max-width: 1024px) {
|
||||
.header-content {
|
||||
flex-direction: column;
|
||||
|
||||
File diff suppressed because it is too large
Load diff
@@ -1,372 +0,0 @@
|
||||
<template>
|
||||
<v-main class="bg-grey-lighten-3">
|
||||
<v-container fluid class="pa-6">
|
||||
|
||||
<!-- Colored Header with Quota Chip -->
|
||||
<v-card class="elevation-4 rounded-xl mb-6 header-banner d-flex align-center justify-space-between pa-4">
|
||||
<div class="d-flex align-center">
|
||||
<v-icon size="48" color="white" class="mr-4">mdi-account-group-outline</v-icon>
|
||||
<h1 class="text-h4 font-weight-bold text-white">Klinik Admin</h1>
|
||||
</div>
|
||||
<v-tooltip text="Jumlah Maksimal Bangku Tersedia" location="bottom">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-chip
|
||||
v-bind="props"
|
||||
class="text-white px-4 py-2"
|
||||
color="green-lighten-1"
|
||||
variant="flat"
|
||||
rounded="xl"
|
||||
>
|
||||
<v-icon start>mdi-chair-rolling</v-icon>
|
||||
Max Quota Bangku 0
|
||||
</v-chip>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</v-card>
|
||||
|
||||
<!-- Loket Admin Table -->
|
||||
<v-card class="mb-6 pa-6 rounded-xl elevation-2">
|
||||
<v-card-title class="d-flex justify-space-between align-center text-h5 font-weight-bold pa-0 mb-4">
|
||||
Loket Admin
|
||||
<div>
|
||||
<v-btn
|
||||
color="green-lighten-1"
|
||||
variant="flat"
|
||||
rounded="xl"
|
||||
class="text-white elevation-4 mr-2 btn-call-group"
|
||||
@click="handleCallClick(1)"
|
||||
>
|
||||
<v-icon start>mdi-numeric-1-box</v-icon>
|
||||
<span class="d-none d-md-inline">Panggil 1 Antrian</span>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="blue-lighten-1"
|
||||
variant="flat"
|
||||
rounded="xl"
|
||||
class="text-white elevation-4 mr-2 btn-call-group"
|
||||
@click="handleCallClick(5)"
|
||||
>
|
||||
<v-icon start>mdi-numeric-5-box</v-icon>
|
||||
<span class="d-none d-md-inline">Panggil 5 Antrian</span>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="orange-lighten-1"
|
||||
variant="flat"
|
||||
rounded="xl"
|
||||
class="text-white elevation-4 mr-2 btn-call-group"
|
||||
@click="handleCallClick(10)"
|
||||
>
|
||||
<v-icon start>mdi-numeric-10-box</v-icon>
|
||||
<span class="d-none d-md-inline">Panggil 10 Antrian</span>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="red-lighten-1"
|
||||
variant="flat"
|
||||
rounded="xl"
|
||||
class="text-white elevation-4 btn-call-group"
|
||||
@click="handleCallClick(20)"
|
||||
>
|
||||
<v-icon start>mdi-numeric-20-box</v-icon>
|
||||
<span class="d-none d-md-inline">Panggil 20 Antrian</span>
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-card-title>
|
||||
|
||||
<!-- Pilihan Show Entries untuk Loket Admin -->
|
||||
<div class="d-flex justify-end mb-4">
|
||||
<div class="d-flex align-center">
|
||||
<span class="mr-2 text-subtitle-1">Show Entries:</span>
|
||||
<v-select
|
||||
:items="[10, 25, 50]"
|
||||
v-model="itemsPerPageLoket"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
hide-details
|
||||
class="show-entries-select"
|
||||
></v-select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<v-table class="mt-3 custom-table rounded-lg elevation-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th v-for="header in loketHeaders" :key="header.text">
|
||||
{{ header.text }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(item, index) in paginatedLoketData" :key="index">
|
||||
<td>{{ item.no }}</td>
|
||||
<td>{{ item.barcode }}</td>
|
||||
<td>{{ item.noRekamedik }}</td>
|
||||
<td>{{ item.noAntrian }}</td>
|
||||
<td>{{ item.shift }}</td>
|
||||
<td>{{ item.ket }}</td>
|
||||
<td>{{ item.fastTrack }}</td>
|
||||
<td>{{ item.pembayaran }}</td>
|
||||
<td>
|
||||
<v-btn size="small" color="primary" class="text-white rounded-lg" @click="handlePanggil(item)">
|
||||
<v-icon start>mdi-phone-incoming</v-icon>
|
||||
Panggil
|
||||
</v-btn>
|
||||
</td>
|
||||
<td>
|
||||
<v-btn size="small" color="red-darken-1" class="text-white rounded-lg" @click="handleBatalkan(item)">
|
||||
<v-icon start>mdi-close</v-icon>
|
||||
Batalkan
|
||||
</v-btn>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</v-table>
|
||||
<div class="d-flex justify-space-between align-center mt-3 pa-2">
|
||||
<span>
|
||||
Menampilkan {{ (currentPageLoket - 1) * itemsPerPageLoket + 1 }} hingga
|
||||
{{ Math.min(currentPageLoket * itemsPerPageLoket, loketData.length) }} dari
|
||||
{{ loketData.length }} entri
|
||||
</span>
|
||||
<div>
|
||||
<v-btn size="small" variant="flat" :disabled="currentPageLoket === 1" class="pagination-btn" @click="handlePageChangeLoket(currentPageLoket - 1)">Previous</v-btn>
|
||||
<v-btn size="small" variant="flat" :disabled="currentPageLoket >= totalPagesLoket" class="pagination-btn" @click="handlePageChangeLoket(currentPageLoket + 1)">Next</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
</v-card>
|
||||
|
||||
<!-- Data Pengunjung Table -->
|
||||
<v-card class="pa-6 rounded-xl elevation-2">
|
||||
<v-card-title class="text-h5 font-weight-bold pa-0 mb-4">
|
||||
Data Pengunjung: Loket
|
||||
</v-card-title>
|
||||
|
||||
<!-- Pilihan Show Entries untuk Data Pengunjung -->
|
||||
<div class="d-flex justify-end mb-4">
|
||||
<div class="d-flex align-center">
|
||||
<span class="mr-2 text-subtitle-1">Show Entries:</span>
|
||||
<v-select
|
||||
:items="[10, 25, 50]"
|
||||
v-model="itemsPerPagePengunjung"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
hide-details
|
||||
class="show-entries-select"
|
||||
></v-select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<v-table class="mt-3 custom-table rounded-lg elevation-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th v-for="header in pengunjungHeaders" :key="header.text">
|
||||
{{ header.text }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(item, index) in paginatedPengunjungData" :key="index">
|
||||
<td>{{ item.no }}</td>
|
||||
<td>{{ item.barcode }}</td>
|
||||
<td>{{ item.noRekamedik }}</td>
|
||||
<td>{{ item.noAntrian }}</td>
|
||||
<td>{{ item.noAntrianKlinik }}</td>
|
||||
<td>{{ item.shift }}</td>
|
||||
<td>{{ item.pembayaran }}</td>
|
||||
<td>
|
||||
<v-chip :color="item.status === 'Selesai' ? 'green' : 'orange'" size="small">
|
||||
{{ item.status }}
|
||||
</v-chip>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</v-table>
|
||||
<div class="d-flex justify-space-between align-center mt-3 pa-2">
|
||||
<span>
|
||||
Menampilkan {{ (currentPagePengunjung - 1) * itemsPerPagePengunjung + 1 }} hingga
|
||||
{{ Math.min(currentPagePengunjung * itemsPerPagePengunjung, pengunjungData.length) }} dari
|
||||
{{ pengunjungData.length }} entri
|
||||
</span>
|
||||
<div>
|
||||
<v-btn size="small" variant="flat" :disabled="currentPagePengunjung === 1" class="pagination-btn" @click="handlePageChangePengunjung(currentPagePengunjung - 1)">Previous</v-btn>
|
||||
<v-btn size="small" variant="flat" :disabled="currentPagePengunjung >= totalPagesPengunjung" class="pagination-btn" @click="handlePageChangePengunjung(currentPagePengunjung + 1)">Next</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
</v-card>
|
||||
</v-container>
|
||||
</v-main>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from "vue";
|
||||
|
||||
definePageMeta({
|
||||
middleware:['auth']
|
||||
})
|
||||
|
||||
// Generate dummy data
|
||||
const generateDummyData = (count) => {
|
||||
const data = [];
|
||||
const shiftOptions = ['Pagi', 'Siang', 'Sore'];
|
||||
const pembayaranOptions = ['BPJS', 'Umum', 'Asuransi'];
|
||||
const statusOptions = ['Menunggu', 'Selesai', 'Di-cancel'];
|
||||
|
||||
for (let i = 1; i <= count; i++) {
|
||||
data.push({
|
||||
no: i,
|
||||
barcode: `B${1000 + i}`,
|
||||
noRekamedik: `RM${100 + i}`,
|
||||
noAntrian: `A${100 + i}`,
|
||||
noAntrianKlinik: `K${100 + i}`,
|
||||
shift: shiftOptions[Math.floor(Math.random() * shiftOptions.length)],
|
||||
ket: "Dummy Data",
|
||||
fastTrack: Math.random() > 0.5 ? "Ya" : "Tidak",
|
||||
pembayaran: pembayaranOptions[Math.floor(Math.random() * pembayaranOptions.length)],
|
||||
status: statusOptions[Math.floor(Math.random() * statusOptions.length)]
|
||||
});
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
// State for Loket Admin table
|
||||
const loketData = ref(generateDummyData(50));
|
||||
const currentPageLoket = ref(1);
|
||||
const itemsPerPageLoket = ref(10);
|
||||
|
||||
// State for Data Pengunjung table
|
||||
const pengunjungData = ref(generateDummyData(50));
|
||||
const currentPagePengunjung = ref(1);
|
||||
const itemsPerPagePengunjung = ref(10);
|
||||
|
||||
// Computed properties for Loket Admin table
|
||||
const paginatedLoketData = computed(() => {
|
||||
const start = (currentPageLoket.value - 1) * itemsPerPageLoket.value;
|
||||
const end = start + itemsPerPageLoket.value;
|
||||
return loketData.value.slice(start, end);
|
||||
});
|
||||
|
||||
const totalPagesLoket = computed(() => {
|
||||
return Math.ceil(loketData.value.length / itemsPerPageLoket.value);
|
||||
});
|
||||
|
||||
// Computed properties for Data Pengunjung table
|
||||
const paginatedPengunjungData = computed(() => {
|
||||
const start = (currentPagePengunjung.value - 1) * itemsPerPagePengunjung.value;
|
||||
const end = start + itemsPerPagePengunjung.value;
|
||||
return pengunjungData.value.slice(start, end);
|
||||
});
|
||||
|
||||
const totalPagesPengunjung = computed(() => {
|
||||
return Math.ceil(pengunjungData.value.length / itemsPerPagePengunjung.value);
|
||||
});
|
||||
|
||||
// Method to handle page change for Loket Admin table
|
||||
const handlePageChangeLoket = (page) => {
|
||||
if (page >= 1 && page <= totalPagesLoket.value) {
|
||||
currentPageLoket.value = page;
|
||||
}
|
||||
};
|
||||
|
||||
// Method to handle page change for Data Pengunjung table
|
||||
const handlePageChangePengunjung = (page) => {
|
||||
if (page >= 1 && page <= totalPagesPengunjung.value) {
|
||||
currentPagePengunjung.value = page;
|
||||
}
|
||||
};
|
||||
|
||||
// Methods to handle button clicks (unchanged)
|
||||
const loketHeaders = [
|
||||
{ text: 'No' },
|
||||
{ text: 'Barcode' },
|
||||
{ text: 'No Rekamedik' },
|
||||
{ text: 'No Antrian' },
|
||||
{ text: 'Shift' },
|
||||
{ text: 'Ket' },
|
||||
{ text: 'Fast Track' },
|
||||
{ text: 'Pembayaran' },
|
||||
{ text: 'Panggil' },
|
||||
{ text: 'Aksi' },
|
||||
];
|
||||
|
||||
const pengunjungHeaders = [
|
||||
{ text: 'No' },
|
||||
{ text: 'Barcode' },
|
||||
{ text: 'No Rekamedik' },
|
||||
{ text: 'No Antrian' },
|
||||
{ text: 'No Antrian Klinik' },
|
||||
{ text: 'Shift' },
|
||||
{ text: 'Pembayaran' },
|
||||
{ text: 'Status' },
|
||||
];
|
||||
|
||||
const handleCallClick = (value) => {
|
||||
console.log(`Panggil ${value} antrian diklik!`);
|
||||
};
|
||||
|
||||
const handlePanggil = (item) => {
|
||||
console.log('Panggil pasien:', item);
|
||||
};
|
||||
|
||||
const handleBatalkan = (item) => {
|
||||
console.log('Batalkan pasien:', item);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* Main container padding */
|
||||
.v-container {
|
||||
max-width: 1400px;
|
||||
}
|
||||
|
||||
.header-banner {
|
||||
background: linear-gradient(45deg, #1A237E, #283593); /* Deep blue gradient */
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* General card styling */
|
||||
.v-card {
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
/* Call buttons group */
|
||||
.btn-call-group {
|
||||
min-width: 50px;
|
||||
height: 40px !important;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Table styling */
|
||||
.custom-table {
|
||||
border: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.custom-table :deep(th) {
|
||||
background-color: #f5f5f5;
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.custom-table :deep(tr:hover) {
|
||||
background-color: #e8eaf6 !important; /* Light blue on hover */
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.custom-table :deep(tbody tr:nth-of-type(odd)) {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.pagination-btn {
|
||||
margin: 0 4px;
|
||||
background-color: #e0e0e0 !important;
|
||||
}
|
||||
|
||||
/* Custom styling for status chip */
|
||||
.v-chip.v-chip--size-small {
|
||||
padding: 4px 8px;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.show-entries-select {
|
||||
max-width: 100px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,754 +0,0 @@
|
||||
<!-- pages/LoketAdmin.vue -->
|
||||
<template>
|
||||
<div class="loket-container">
|
||||
<!-- Header Section -->
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<div class="header-left">
|
||||
<div class="header-icon">
|
||||
<v-icon size="32" color="white">mdi-view-dashboard</v-icon>
|
||||
</div>
|
||||
<div class="header-text">
|
||||
<h1 class="page-title">Loket Admin</h1>
|
||||
<p class="page-subtitle">Rabu, 13 Agustus 2025 - Pelayanan</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<v-chip color="success" variant="flat" class="mr-2">
|
||||
Total {{ totalPasien }} Pasien
|
||||
</v-chip>
|
||||
<v-chip color="white" variant="flat" class="text-primary">
|
||||
Max 150 Pasien
|
||||
</v-chip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<v-card class="next-patient-card mb-4" elevation="2">
|
||||
<v-card-text class="text-center pa-6">
|
||||
<v-chip color="success" variant="flat" class="mb-3" size="large">
|
||||
<v-icon start>mdi-account-arrow-right</v-icon>
|
||||
PASIEN SELANJUTNYA
|
||||
</v-chip>
|
||||
<div class="text-h4 font-weight-bold mb-2 text-success">
|
||||
{{ nextPatient ? nextPatient.noAntrian.split(" |")[0] : "UM1004" }}
|
||||
</div>
|
||||
<div class="text-body-1 mb-4 text-grey-darken-1">
|
||||
Klik tombol hijau untuk memanggil
|
||||
</div>
|
||||
<v-btn
|
||||
color="success"
|
||||
variant="flat"
|
||||
size="large"
|
||||
class="px-8"
|
||||
@click="callNext"
|
||||
:disabled="!nextPatient"
|
||||
>
|
||||
<v-icon start>mdi-microphone</v-icon>
|
||||
PANGGIL NEXT
|
||||
</v-btn>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</div>
|
||||
<!-- Combined Control Section -->
|
||||
<v-row class="mb-4">
|
||||
<!-- Left Side: Call Controls and Current Processing -->
|
||||
<v-col cols="12" lg="6">
|
||||
<!-- Current Patient Processing Card -->
|
||||
<v-card
|
||||
v-if="currentProcessingPatient"
|
||||
class="current-processing-card"
|
||||
elevation="2"
|
||||
>
|
||||
<v-card-text class="pa-4">
|
||||
<div class="d-flex align-center justify-space-between">
|
||||
<div class="patient-info">
|
||||
<v-chip color="primary" variant="flat" class="mb-2">
|
||||
<v-icon start>mdi-account-clock</v-icon>
|
||||
SEDANG DIPROSES
|
||||
</v-chip>
|
||||
<div class="text-h5 font-weight-bold mb-1">
|
||||
{{ currentProcessingPatient.noAntrian.split(" |")[0] }}
|
||||
</div>
|
||||
<div class="text-subtitle-1 text-grey-darken-1">
|
||||
{{ currentProcessingPatient.barcode }} |
|
||||
{{ currentProcessingPatient.klinik }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="action-buttons">
|
||||
<v-btn
|
||||
color="success"
|
||||
variant="flat"
|
||||
class="mr-2"
|
||||
@click="processPatient(currentProcessingPatient, 'check-in')"
|
||||
>
|
||||
<v-icon start>mdi-check-circle</v-icon>
|
||||
Check In
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="warning"
|
||||
variant="flat"
|
||||
class="mr-2"
|
||||
@click="processPatient(currentProcessingPatient, 'terlambat')"
|
||||
>
|
||||
<v-icon start>mdi-clock-alert</v-icon>
|
||||
Terlambat
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="error"
|
||||
variant="flat"
|
||||
@click="processPatient(currentProcessingPatient, 'pending')"
|
||||
>
|
||||
<v-icon start>mdi-pause-circle</v-icon>
|
||||
Pending
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<!-- Right Side: Patient Queue Info -->
|
||||
<v-col cols="12" lg="6">
|
||||
<!-- Next Patient Card -->
|
||||
<!-- Quota Info Card -->
|
||||
<v-card class="quota-info-card" elevation="2">
|
||||
<v-card-text class="pa-4">
|
||||
<div class="text-center">
|
||||
<div class="text-h6 font-weight-medium mb-3">
|
||||
Panggil Antrean Anjungan
|
||||
</div>
|
||||
<v-row class="mb-3">
|
||||
<v-col cols="6" class="text-center">
|
||||
<div class="text-caption text-grey-darken-1">Kuota</div>
|
||||
<div class="text-h4 font-weight-bold">150</div>
|
||||
</v-col>
|
||||
<v-col cols="6" class="text-center">
|
||||
<div class="text-caption text-grey-darken-1">Tersedia</div>
|
||||
<div class="text-h4 font-weight-bold text-success">
|
||||
{{ 150 - quotaUsed }}
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<div class="text-body-2 text-grey-darken-1 mb-2">
|
||||
Total Quota Terpakai: {{ quotaUsed }}
|
||||
</div>
|
||||
<v-progress-linear
|
||||
:model-value="(quotaUsed / 150) * 100"
|
||||
color="success"
|
||||
height="8"
|
||||
rounded
|
||||
></v-progress-linear>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
<v-card-text class="call-controls-card align-center py-4">
|
||||
<div class="text-subtitle-1 font-weight-medium"
|
||||
>Panggil Pasien:</div>
|
||||
<div class="d-flex align-center flex-wrap mb-3">
|
||||
<v-btn
|
||||
color="success"
|
||||
variant="flat"
|
||||
size="large"
|
||||
class="px-6 ma-4"
|
||||
@click="callMultiplePatients(1)"
|
||||
>
|
||||
<span class="text-h6 font-weight-bold">1</span>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="info"
|
||||
variant="flat"
|
||||
size="large"
|
||||
class="px-6 ma-4"
|
||||
@click="callMultiplePatients(5)"
|
||||
>
|
||||
<span class="text-h6 font-weight-bold">5</span>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="warning"
|
||||
variant="flat"
|
||||
size="large"
|
||||
class="px-6 ma-4"
|
||||
@click="callMultiplePatients(10)"
|
||||
>
|
||||
<span class="text-h6 font-weight-bold">10</span>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="error"
|
||||
variant="flat"
|
||||
size="large"
|
||||
class="px-6 ma-4"
|
||||
@click="callMultiplePatients(20)"
|
||||
>
|
||||
<span class="text-h6 font-weight-bold">20</span>
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- Di Loket Patients Table -->
|
||||
<v-card class="main-table-card mb-4" elevation="2">
|
||||
<TabelData
|
||||
:headers="diLoketHeaders"
|
||||
:items="diLoketPatients"
|
||||
title="DATA PASIEN - DI LOKET"
|
||||
>
|
||||
<template #actions="{ item }">
|
||||
<div class="d-flex gap-1">
|
||||
<v-btn
|
||||
size="small"
|
||||
color="info"
|
||||
variant="flat"
|
||||
@click="processPatient(item, 'aktifkan')"
|
||||
>
|
||||
Aktifkan
|
||||
</v-btn>
|
||||
<v-btn
|
||||
size="small"
|
||||
color="success"
|
||||
variant="flat"
|
||||
@click="processPatient(item, 'proses')"
|
||||
>
|
||||
Proses
|
||||
</v-btn>
|
||||
</div>
|
||||
</template>
|
||||
<template #item.jamPanggil="{ item }">
|
||||
<span :class="getRowClass(item)">{{ item.jamPanggil }}</span>
|
||||
</template>
|
||||
</TabelData>
|
||||
</v-card>
|
||||
|
||||
<!-- Terlambat Patients Table -->
|
||||
<v-card
|
||||
class="late-table-card mb-4"
|
||||
elevation="2"
|
||||
v-if="terlambatPatients.length > 0"
|
||||
>
|
||||
<TabelData
|
||||
:headers="terlambatHeaders"
|
||||
:items="terlambatPatients"
|
||||
title="INFO PASIEN LAPOR TERLAMBAT"
|
||||
>
|
||||
<template #actions="{ item }">
|
||||
<div class="d-flex gap-1">
|
||||
<v-btn
|
||||
size="small"
|
||||
color="success"
|
||||
variant="flat"
|
||||
@click="processPatient(item, 'aktifkan')"
|
||||
>
|
||||
Aktifkan
|
||||
</v-btn>
|
||||
</div>
|
||||
</template>
|
||||
</TabelData>
|
||||
</v-card>
|
||||
|
||||
<!-- Pending Patients Table -->
|
||||
<v-card
|
||||
class="pending-table-card mb-4"
|
||||
elevation="2"
|
||||
v-if="pendingPatients.length > 0"
|
||||
>
|
||||
<TabelData
|
||||
:headers="pendingHeaders"
|
||||
:items="pendingPatients"
|
||||
title="INFO PASIEN PENDING"
|
||||
>
|
||||
<template #actions="{ item }">
|
||||
<div class="d-flex gap-1">
|
||||
<v-btn
|
||||
size="small"
|
||||
color="success"
|
||||
variant="flat"
|
||||
@click="processPatient(item, 'proses')"
|
||||
>
|
||||
Proses
|
||||
</v-btn>
|
||||
</div>
|
||||
</template>
|
||||
</TabelData>
|
||||
</v-card>
|
||||
|
||||
<!-- Snackbar -->
|
||||
<v-snackbar
|
||||
v-model="snackbar"
|
||||
:color="snackbarColor"
|
||||
:timeout="3000"
|
||||
location="top right"
|
||||
>
|
||||
{{ snackbarText }}
|
||||
|
||||
<template v-slot:actions>
|
||||
<v-btn icon @click="snackbar = false">
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from "vue";
|
||||
import TabelData from "../components/TabelData.vue";
|
||||
|
||||
// Reactive data
|
||||
const snackbar = ref(false);
|
||||
const snackbarText = ref("");
|
||||
const snackbarColor = ref("success");
|
||||
const quotaUsed = ref(5);
|
||||
const currentProcessingPatient = ref(null);
|
||||
|
||||
// Base patient data - semua pasien yang belum dipanggil
|
||||
const allPatients = ref([
|
||||
{
|
||||
no: 1,
|
||||
jamPanggil: "12:49",
|
||||
barcode: "250811100163",
|
||||
noAntrian: "UM1001 | Online - 250811100163",
|
||||
shift: "Shift 1",
|
||||
klinik: "KANDUNGAN",
|
||||
fastTrack: "UMUM",
|
||||
pembayaran: "UMUM",
|
||||
status: "waiting", // waiting, di-loket, terlambat, pending, processed
|
||||
},
|
||||
{
|
||||
no: 2,
|
||||
jamPanggil: "10:52",
|
||||
barcode: "250811100155",
|
||||
noAntrian: "UM1002 | Online - 250811100155",
|
||||
shift: "Shift 1",
|
||||
klinik: "IPD",
|
||||
fastTrack: "UMUM",
|
||||
pembayaran: "UMUM",
|
||||
status: "waiting",
|
||||
},
|
||||
{
|
||||
no: 3,
|
||||
jamPanggil: "09:30",
|
||||
barcode: "250811100200",
|
||||
noAntrian: "UM1003 | Online - 250811100200",
|
||||
shift: "Shift 1",
|
||||
klinik: "SARAF",
|
||||
fastTrack: "UMUM",
|
||||
pembayaran: "UMUM",
|
||||
status: "waiting",
|
||||
},
|
||||
{
|
||||
no: 4,
|
||||
jamPanggil: "14:15",
|
||||
barcode: "250811100210",
|
||||
noAntrian: "UM1004 | Online - 250811100210",
|
||||
shift: "Shift 1",
|
||||
klinik: "THT",
|
||||
fastTrack: "UMUM",
|
||||
pembayaran: "UMUM",
|
||||
status: "waiting",
|
||||
},
|
||||
...Array.from({ length: 16 }, (_, i) => ({
|
||||
no: i + 5,
|
||||
jamPanggil: `${String(Math.floor(Math.random() * 12) + 1).padStart(2, "0")}:${String(Math.floor(Math.random() * 60)).padStart(2, "0")}`,
|
||||
barcode: `25081110${String(i + 300).padStart(4, "0")}`,
|
||||
noAntrian: `UM100${i + 5} | Online - 25081110${String(i + 300).padStart(4, "0")}`,
|
||||
shift: "Shift 1",
|
||||
klinik: ["KANDUNGAN", "IPD", "THT", "SARAF"][Math.floor(Math.random() * 4)],
|
||||
fastTrack: "UMUM",
|
||||
pembayaran: "UMUM",
|
||||
status: "waiting",
|
||||
})),
|
||||
]);
|
||||
|
||||
// Computed properties for different status tables
|
||||
const diLoketPatients = computed(() =>
|
||||
allPatients.value.filter((patient) => patient.status === "di-loket")
|
||||
);
|
||||
|
||||
const terlambatPatients = computed(() =>
|
||||
allPatients.value.filter((patient) => patient.status === "terlambat")
|
||||
);
|
||||
|
||||
const pendingPatients = computed(() =>
|
||||
allPatients.value.filter((patient) => patient.status === "pending")
|
||||
);
|
||||
|
||||
const nextPatient = computed(() => {
|
||||
return allPatients.value.find((patient) => patient.status === "waiting");
|
||||
});
|
||||
|
||||
const totalPasien = computed(() => allPatients.value.length);
|
||||
|
||||
// Headers for different tables
|
||||
const diLoketHeaders = ref([
|
||||
{ title: "No", value: "no", sortable: false, width: "60px" },
|
||||
{ title: "Jam Panggil", value: "jamPanggil", sortable: true, width: "100px" },
|
||||
{ title: "Barcode", value: "barcode", sortable: true, width: "140px" },
|
||||
{ title: "No Antrian", value: "noAntrian", sortable: true, width: "200px" },
|
||||
{ title: "Shift", value: "shift", sortable: true, width: "80px" },
|
||||
{ title: "Klinik", value: "klinik", sortable: true, width: "120px" },
|
||||
{ title: "Fast Track", value: "fastTrack", sortable: true, width: "100px" },
|
||||
{ title: "Pembayaran", value: "pembayaran", sortable: true, width: "100px" },
|
||||
{ title: "Aksi", value: "aksi", sortable: false, width: "200px" },
|
||||
]);
|
||||
|
||||
const terlambatHeaders = ref([
|
||||
{ title: "No", value: "no", sortable: false, width: "60px" },
|
||||
{ title: "Barcode", value: "barcode", sortable: true, width: "140px" },
|
||||
{ title: "No Antrian", value: "noAntrian", sortable: true, width: "200px" },
|
||||
{ title: "Shift", value: "shift", sortable: true, width: "80px" },
|
||||
{ title: "Klinik", value: "klinik", sortable: true, width: "120px" },
|
||||
{ title: "Aksi", value: "aksi", sortable: false, width: "100px" },
|
||||
]);
|
||||
|
||||
const pendingHeaders = ref([
|
||||
{ title: "#", value: "no", sortable: false, width: "60px" },
|
||||
{ title: "Barcode", value: "barcode", sortable: true, width: "140px" },
|
||||
{ title: "No Antrian", value: "noAntrian", sortable: true, width: "200px" },
|
||||
{ title: "Shift", value: "shift", sortable: true, width: "80px" },
|
||||
{ title: "Klinik", value: "klinik", sortable: true, width: "120px" },
|
||||
{ title: "Fast Track", value: "fastTrack", sortable: true, width: "100px" },
|
||||
{ title: "Pembayaran", value: "pembayaran", sortable: true, width: "100px" },
|
||||
{ title: "Aksi", value: "aksi", sortable: false, width: "100px" },
|
||||
]);
|
||||
|
||||
// Methods
|
||||
const showSnackbar = (text, color = "success") => {
|
||||
snackbarText.value = text;
|
||||
snackbarColor.value = color;
|
||||
snackbar.value = true;
|
||||
};
|
||||
|
||||
const callMultiplePatients = (count) => {
|
||||
const waitingPatients = allPatients.value.filter(
|
||||
(patient) => patient.status === "waiting"
|
||||
);
|
||||
const patientsToCall = waitingPatients.slice(0, count);
|
||||
|
||||
if (patientsToCall.length === 0) {
|
||||
showSnackbar("Tidak ada pasien yang menunggu", "warning");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check quota
|
||||
if (quotaUsed.value + patientsToCall.length > 150) {
|
||||
showSnackbar("Quota tidak mencukupi", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
// Move patients to "di-loket" status
|
||||
patientsToCall.forEach((patient) => {
|
||||
patient.status = "di-loket";
|
||||
});
|
||||
|
||||
quotaUsed.value += patientsToCall.length;
|
||||
showSnackbar(`Memanggil ${patientsToCall.length} pasien ke loket`, "success");
|
||||
};
|
||||
|
||||
const callNext = () => {
|
||||
if (!nextPatient.value) {
|
||||
showSnackbar("Tidak ada pasien selanjutnya", "warning");
|
||||
return;
|
||||
}
|
||||
|
||||
if (quotaUsed.value >= 150) {
|
||||
showSnackbar("Quota sudah penuh", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
// Move next patient to processing
|
||||
nextPatient.value.status = "di-loket";
|
||||
currentProcessingPatient.value = nextPatient.value;
|
||||
quotaUsed.value++;
|
||||
|
||||
showSnackbar(
|
||||
`Memanggil pasien ${nextPatient.value.noAntrian.split(" |")[0]}`,
|
||||
"success"
|
||||
);
|
||||
};
|
||||
|
||||
const processPatient = (patient, action) => {
|
||||
const patientCode = patient.noAntrian.split(" |")[0];
|
||||
|
||||
switch (action) {
|
||||
case "check-in":
|
||||
patient.status = "processed";
|
||||
if (currentProcessingPatient.value?.no === patient.no) {
|
||||
currentProcessingPatient.value = null;
|
||||
}
|
||||
showSnackbar(`Pasien ${patientCode} berhasil check in`, "success");
|
||||
break;
|
||||
|
||||
case "terlambat":
|
||||
patient.status = "terlambat";
|
||||
if (currentProcessingPatient.value?.no === patient.no) {
|
||||
currentProcessingPatient.value = null;
|
||||
}
|
||||
showSnackbar(`Pasien ${patientCode} ditandai terlambat`, "warning");
|
||||
break;
|
||||
|
||||
case "pending":
|
||||
patient.status = "pending";
|
||||
if (currentProcessingPatient.value?.no === patient.no) {
|
||||
currentProcessingPatient.value = null;
|
||||
}
|
||||
showSnackbar(`Pasien ${patientCode} di-pending`, "info");
|
||||
break;
|
||||
|
||||
case "aktifkan":
|
||||
if (patient.status === "terlambat") {
|
||||
patient.status = "di-loket";
|
||||
showSnackbar(`Pasien ${patientCode} diaktifkan kembali`, "success");
|
||||
}
|
||||
break;
|
||||
|
||||
case "proses":
|
||||
currentProcessingPatient.value = patient;
|
||||
showSnackbar(`Memproses pasien ${patientCode}`, "info");
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const getRowClass = (item) => {
|
||||
if (item.status === "current") {
|
||||
return "text-success font-weight-bold";
|
||||
}
|
||||
return "";
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.loket-container {
|
||||
background: #f5f7fa;
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
background: linear-gradient(135deg, #1976d2 0%, #1565c0 100%);
|
||||
border-radius: 16px;
|
||||
margin-bottom: 24px;
|
||||
box-shadow: 0 8px 32px rgba(25, 118, 210, 0.3);
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 32px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 16px;
|
||||
padding: 16px;
|
||||
margin-right: 20px;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
margin: 4px 0 0 0;
|
||||
opacity: 0.9;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.call-controls-card,
|
||||
.next-patient-card,
|
||||
.current-processing-card,
|
||||
.quota-info-card,
|
||||
.main-table-card,
|
||||
.late-table-card,
|
||||
.pending-table-card {
|
||||
border-radius: 16px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.next-patient-card {
|
||||
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||||
border: 2px solid rgba(76, 175, 80, 0.2);
|
||||
}
|
||||
|
||||
.current-processing-card {
|
||||
background: linear-gradient(135deg, #fff3e0 0%, #ffe0b2 100%);
|
||||
border: 2px solid rgba(255, 152, 0, 0.2);
|
||||
}
|
||||
|
||||
.quota-info-card {
|
||||
background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%);
|
||||
border: 2px solid rgba(33, 150, 243, 0.2);
|
||||
}
|
||||
|
||||
.patient-info .text-h5 {
|
||||
color: #1976d2;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* Enhanced table styling */
|
||||
.main-table-card :deep(.v-data-table th),
|
||||
.late-table-card :deep(.v-data-table th),
|
||||
.pending-table-card :deep(.v-data-table th) {
|
||||
background: #fafbfc;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
color: #374151;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.main-table-card :deep(.v-data-table tbody tr:hover),
|
||||
.late-table-card :deep(.v-data-table tbody tr:hover),
|
||||
.pending-table-card :deep(.v-data-table tbody tr:hover) {
|
||||
background: #f8fafc !important;
|
||||
}
|
||||
|
||||
.main-table-card :deep(.v-data-table tbody td),
|
||||
.late-table-card :deep(.v-data-table tbody td),
|
||||
.pending-table-card :deep(.v-data-table tbody td) {
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid #f1f5f9;
|
||||
}
|
||||
|
||||
/* Button styling */
|
||||
.v-btn {
|
||||
text-transform: none !important;
|
||||
}
|
||||
|
||||
.v-btn--size-small {
|
||||
height: 32px;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
/* Success text color */
|
||||
.text-success {
|
||||
color: #4caf50 !important;
|
||||
}
|
||||
|
||||
/* Responsive Design */
|
||||
@media (max-width: 1024px) {
|
||||
.header-content {
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.current-processing-card .d-flex {
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.action-buttons .v-btn {
|
||||
flex: 1;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
/* Stack layout vertically on medium screens */
|
||||
.call-controls-card .d-flex {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.loket-container {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
padding: 24px 20px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.call-controls-card .d-flex {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.call-controls-card .v-btn {
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
/* Stack all control buttons vertically on mobile */
|
||||
.call-controls-card .d-flex.flex-wrap {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.call-controls-card .v-btn {
|
||||
width: 100%;
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.action-buttons .v-btn {
|
||||
width: 100%;
|
||||
margin: 4px 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.page-title {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.next-patient-card .text-h4 {
|
||||
font-size: 1.75rem !important;
|
||||
}
|
||||
|
||||
.current-processing-card .patient-info .text-h5 {
|
||||
font-size: 1.25rem !important;
|
||||
}
|
||||
|
||||
.quota-info-card .text-h4 {
|
||||
font-size: 1.5rem !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,526 +0,0 @@
|
||||
<template>
|
||||
<!-- Main Content -->
|
||||
<v-main class="bg-grey-lighten-3">
|
||||
<v-container fluid class="pa-6 main-content-padding">
|
||||
<!-- Header Banner & Stats -->
|
||||
<v-card class="d-flex justify-space-between align-center pa-5 rounded-xl elevation-4 mb-6 header-banner">
|
||||
<div class="d-flex align-center">
|
||||
<v-icon size="40" class="mr-3 text-white">mdi-hospital-box-outline</v-icon>
|
||||
<span class="text-h4 font-weight-bold text-white">Loket Admin </span>
|
||||
</div>
|
||||
<div class="d-flex align-center text-white text-end flex-wrap justify-end">
|
||||
<span class="mr-4">Loket 24</span>
|
||||
<span class="mr-4">{{ currentDateLongFormatted }}</span>
|
||||
<span>{{ currentDateShortFormatted }} - Pelayanan</span>
|
||||
</div>
|
||||
</v-card>
|
||||
|
||||
<!-- Status Cards Section -->
|
||||
<v-row class="mb-6">
|
||||
<!-- Panggil 1 Antrian Card -->
|
||||
<v-col cols="12" sm="6" md="3">
|
||||
<v-card
|
||||
class="pa-4 rounded-xl elevation-2 text-center"
|
||||
color="#4CAF50"
|
||||
@click="handleStatusCardClick(1)"
|
||||
>
|
||||
<v-card-text class="text-white">
|
||||
<div class="text-h4 font-weight-bold">1</div>
|
||||
<div class="text-subtitle-1 mt-1">Panggil</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
<!-- Panggil 5 Antrian Card -->
|
||||
<v-col cols="12" sm="6" md="3">
|
||||
<v-card
|
||||
class="pa-4 rounded-xl elevation-2 text-center"
|
||||
color="#4CAF50"
|
||||
@click="handleStatusCardClick(5)"
|
||||
>
|
||||
<v-card-text class="text-white">
|
||||
<div class="text-h4 font-weight-bold">5</div>
|
||||
<div class="text-subtitle-1 mt-1">Panggil</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
<!-- Panggil 10 Antrian Card -->
|
||||
<v-col cols="12" sm="6" md="3">
|
||||
<v-card
|
||||
class="pa-4 rounded-xl elevation-2 text-center"
|
||||
color="#4CAF50"
|
||||
@click="handleStatusCardClick(10)"
|
||||
>
|
||||
<v-card-text class="text-white">
|
||||
<div class="text-h4 font-weight-bold">10</div>
|
||||
<div class="text-subtitle-1 mt-1">Panggil</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
<!-- Panggil 20 Antrian Card -->
|
||||
<v-col cols="12" sm="6" md="3">
|
||||
<v-card
|
||||
class="pa-4 rounded-xl elevation-2 text-center"
|
||||
color="#4CAF50"
|
||||
@click="handleStatusCardClick(20)"
|
||||
>
|
||||
<v-card-text class="text-white">
|
||||
<div class="text-h4 font-weight-bold">20</div>
|
||||
<div class="text-subtitle-1 mt-1">Panggil</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- Next Patient Card -->
|
||||
<v-card class="next-patient-card d-flex align-center justify-center pa-8 text-center rounded-xl elevation-6 mb-6">
|
||||
<div class="text-center">
|
||||
<div class="text-h4 text-white">NEXT PATIENT</div>
|
||||
<div class="text-h2 font-weight-bold text-white mt-2">UM1001</div>
|
||||
<v-btn
|
||||
size="large"
|
||||
color="#00A896"
|
||||
class="mt-4 text-white"
|
||||
@click="handleNextPatientClick"
|
||||
>
|
||||
<v-icon start>mdi-arrow-right-circle</v-icon>
|
||||
Panggil Pasien Selanjutnya
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-card>
|
||||
|
||||
<!-- Main Data Table -->
|
||||
<v-card class="mb-6 pa-6 rounded-xl elevation-2">
|
||||
<v-card-title class="d-flex justify-space-between align-center text-h5 font-weight-bold pa-0 mb-4">
|
||||
Data Pasien
|
||||
<div class="d-flex align-center">
|
||||
<span class="mr-2 text-caption">Show</span>
|
||||
<v-select
|
||||
v-model="itemsPerPage"
|
||||
:items="[10, 25, 50, 100]"
|
||||
density="compact"
|
||||
variant="solo"
|
||||
flat
|
||||
hide-details
|
||||
class="mx-2 select-items"
|
||||
></v-select>
|
||||
<span class="mr-2 text-caption">entries</span>
|
||||
<v-text-field
|
||||
v-model="search"
|
||||
density="compact"
|
||||
variant="solo"
|
||||
flat
|
||||
hide-details
|
||||
append-inner-icon="mdi-magnify"
|
||||
label="Search"
|
||||
style="max-width: 200px"
|
||||
></v-text-field>
|
||||
</div>
|
||||
</v-card-title>
|
||||
<v-data-table
|
||||
:headers="mainHeaders"
|
||||
:items="mainPatients"
|
||||
:search="search"
|
||||
:items-per-page="itemsPerPage"
|
||||
:row-class="getRowClass"
|
||||
class="custom-table"
|
||||
>
|
||||
<!-- Custom template for the 'aksi' column -->
|
||||
<template v-slot:item.aksi="{ item }">
|
||||
<div class="d-flex ga-1">
|
||||
<!-- Show different buttons based on the item's status -->
|
||||
<template v-if="item.status === 'dipanggil'">
|
||||
<v-btn size="small" color="primary" variant="flat" class="rounded-lg" @click="handleProsesClick(item)">
|
||||
<v-icon start>mdi-cogs</v-icon>Proses
|
||||
</v-btn>
|
||||
<v-btn size="small" color="success" variant="flat" class="rounded-lg">
|
||||
<v-icon start>mdi-check-circle-outline</v-icon>Selesai
|
||||
</v-btn>
|
||||
</template>
|
||||
<template v-else-if="item.status === 'dalam_proses'">
|
||||
<v-btn size="small" color="success" variant="flat" class="rounded-lg">
|
||||
<v-icon start>mdi-check-circle-outline</v-icon>Selesai
|
||||
</v-btn>
|
||||
<v-btn size="small" color="warning" variant="flat" class="rounded-lg">
|
||||
<v-icon start>mdi-pause-circle-outline</v-icon>Tunda
|
||||
</v-btn>
|
||||
<v-btn size="small" color="error" variant="flat" class="rounded-lg">
|
||||
<v-icon start>mdi-close-circle-outline</v-icon>Batal
|
||||
</v-btn>
|
||||
</template>
|
||||
<template v-else>
|
||||
<v-btn size="small" color="primary" variant="flat" class="rounded-lg">
|
||||
<v-icon start>mdi-cogs</v-icon>Proses
|
||||
</v-btn>
|
||||
<v-btn size="small" color="success" variant="flat" class="rounded-lg">
|
||||
<v-icon start>mdi-check-circle-outline</v-icon>Selesai
|
||||
</v-btn>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<!-- Custom template for the 'panggil' column -->
|
||||
<template v-slot:item.panggil="{ item }">
|
||||
<v-btn
|
||||
size="small"
|
||||
:color="item.status === 'dalam_proses' ? 'grey' : 'info'"
|
||||
variant="flat"
|
||||
class="rounded-lg"
|
||||
@click="handlePanggilClick(item)"
|
||||
:disabled="item.status === 'dalam_proses'"
|
||||
>
|
||||
<v-icon start>mdi-phone</v-icon>Panggil
|
||||
</v-btn>
|
||||
</template>
|
||||
<!-- Custom template for the 'noAntrian' column -->
|
||||
<template v-slot:item.noAntrian="{ item }">
|
||||
<span :class="{'online-antrian': item.status === 'dipanggil'}">{{ item.noAntrian }}</span>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</v-card>
|
||||
|
||||
<!-- Late Patients Table -->
|
||||
<v-card class="mb-6 pa-6 rounded-xl elevation-2">
|
||||
<v-card-title class="d-flex justify-space-between align-center text-h5 font-weight-bold pa-0 mb-4">
|
||||
Info Pasien Lapor Terlambat
|
||||
<div class="d-flex align-center">
|
||||
<span class="mr-2 text-caption text-orange">KETERANGAN: PASIEN MASUK PADA TANGGAL</span>
|
||||
<span class="mr-2 text-caption">Show</span>
|
||||
<v-select
|
||||
v-model="lateItemsPerPage"
|
||||
:items="[10, 25, 50, 100]"
|
||||
density="compact"
|
||||
variant="solo"
|
||||
flat
|
||||
hide-details
|
||||
class="mx-2 select-items"
|
||||
></v-select>
|
||||
<span class="mr-2 text-caption">entries</span>
|
||||
<v-text-field
|
||||
v-model="lateSearch"
|
||||
density="compact"
|
||||
variant="solo"
|
||||
flat
|
||||
hide-details
|
||||
append-inner-icon="mdi-magnify"
|
||||
label="Search"
|
||||
style="max-width: 200px"
|
||||
></v-text-field>
|
||||
</div>
|
||||
</v-card-title>
|
||||
<v-data-table
|
||||
:headers="lateHeaders"
|
||||
:items="latePatients"
|
||||
:search="lateSearch"
|
||||
:items-per-page="lateItemsPerPage"
|
||||
class="custom-table"
|
||||
>
|
||||
<template v-slot:no-data>
|
||||
<div class="text-center pa-4">Tidak ada data yang tersedia</div>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</v-card>
|
||||
|
||||
<!-- Clinic Entry Patients Table -->
|
||||
<v-card class="mb-6 pa-6 rounded-xl elevation-2">
|
||||
<v-card-title class="d-flex justify-space-between align-center text-h5 font-weight-bold pa-0 mb-4">
|
||||
Info Pasien Masuk Klinik
|
||||
</v-card-title>
|
||||
<v-data-table
|
||||
:headers="clinicHeaders"
|
||||
:items="clinicPatients"
|
||||
:search="clinicSearch"
|
||||
:items-per-page="clinicItemsPerPage"
|
||||
class="custom-table"
|
||||
>
|
||||
<template v-slot:no-data>
|
||||
<div class="text-center pa-4">Tidak ada data yang tersedia</div>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</v-card>
|
||||
|
||||
<!-- Info Klinik Table -->
|
||||
<v-card class="pa-6 rounded-xl elevation-2">
|
||||
<v-card-title class="text-h5 font-weight-bold pa-0 mb-4">
|
||||
Info Klinik
|
||||
</v-card-title>
|
||||
<v-data-table
|
||||
:headers="infoKlinikHeaders"
|
||||
:items="infoKlinikData"
|
||||
class="custom-table"
|
||||
hide-default-footer
|
||||
disable-pagination
|
||||
>
|
||||
<template v-slot:bottom>
|
||||
<v-card-text class="d-flex justify-end text-right">
|
||||
<span class="mr-4 font-weight-bold text-h6">Total:</span>
|
||||
<span class="mr-12 font-weight-bold text-h6 text-primary">{{ totalDapatDipanggil }}</span>
|
||||
<span class="font-weight-bold text-h6 text-primary">{{ totalShiftBelumBuka }}</span>
|
||||
</v-card-text>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</v-card>
|
||||
</v-container>
|
||||
</v-main>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, computed } from "vue";
|
||||
|
||||
definePageMeta({
|
||||
middleware:['auth']
|
||||
})
|
||||
|
||||
// Reactive data
|
||||
const search = ref("");
|
||||
const lateSearch = ref("");
|
||||
const clinicSearch = ref("");
|
||||
const itemsPerPage = ref(10);
|
||||
const lateItemsPerPage = ref(10);
|
||||
const clinicItemsPerPage = ref(10);
|
||||
const currentDateLongFormatted = ref("");
|
||||
const currentDateShortFormatted = ref("");
|
||||
|
||||
// Table headers
|
||||
const mainHeaders = ref([
|
||||
{ title: "No", value: "no", sortable: false },
|
||||
{ title: "Jam Panggil", value: "jamPanggil" },
|
||||
{ title: "Barcode", value: "barcode" },
|
||||
{ title: "No Antrian", value: "noAntrian" },
|
||||
{ title: "Shift", value: "shift" },
|
||||
{ title: "Klinik", value: "klinik" },
|
||||
{ title: "Fast Track", value: "fastTrack" },
|
||||
{ title: "Pembayaran", value: "pembayaran" },
|
||||
{ title: "Panggil", align: 'center', value: "panggil", sortable: false },
|
||||
{ title: "Aksi", value: "aksi", sortable: false },
|
||||
]);
|
||||
|
||||
const lateHeaders = ref([
|
||||
{ title: "No", value: "no", sortable: false },
|
||||
{ title: "Barcode", value: "barcode" },
|
||||
{ title: "No Antrian", value: "noAntrian" },
|
||||
{ title: "Shift", value: "shift" },
|
||||
{ title: "Klinik", value: "klinik" },
|
||||
{ title: "Aksi", value: "aksi", sortable: false },
|
||||
]);
|
||||
|
||||
const clinicHeaders = ref([
|
||||
{ title: "#", value: "no", sortable: false },
|
||||
{ title: "Barcode", value: "barcode" },
|
||||
{ title: "No Antrian", value: "noAntrian" },
|
||||
{ title: "No RM", value: "noRM" },
|
||||
{ title: "Shift", value: "shift" },
|
||||
{ title: "Klinik", value: "klinik" },
|
||||
{ title: "Fast Track", value: "fastTrack" },
|
||||
{ title: "Pembayaran", value: "pembayaran" },
|
||||
{ title: "Aksi", value: "aksi", sortable: false },
|
||||
]);
|
||||
|
||||
const infoKlinikHeaders = ref([
|
||||
{ title: "#", value: "no" },
|
||||
{ title: "Klinik", value: "klinik" },
|
||||
{ title: "Jumlah Shift", value: "jumlahShift" },
|
||||
{ title: "Quota Per Shift", value: "quotaPerShift" },
|
||||
{ title: "Status", value: "status" },
|
||||
{ title: "Dapat Di Panggil", value: "dapatDiPanggil" },
|
||||
{ title: "Shift Belum Buka", value: "shiftBelumBuka" },
|
||||
]);
|
||||
|
||||
// Sample data with new 'originalAntrian' and 'status' properties
|
||||
const mainPatients = ref([
|
||||
{ no: 1, jamPanggil: "11:46", barcode: "250826100362", noAntrian: "UM1002 | Online - 250826100362", originalAntrian: "UM1002", shift: "Shift 1", klinik: "IPD", fastTrack: "", pembayaran: "UMUM", panggil: "Panggil", status: "dipanggil" },
|
||||
{ no: 2, jamPanggil: "06:47", barcode: "250826100140", noAntrian: "UM1003", originalAntrian: "UM1003", shift: "Shift 1", klinik: "IPD", fastTrack: "", pembayaran: "UMUM", panggil: "Panggil", status: "" },
|
||||
{ no: 3, jamPanggil: "06:47", barcode: "250826100143", noAntrian: "UM1004", originalAntrian: "UM1004", shift: "Shift 1", klinik: "IPD", fastTrack: "", pembayaran: "UMUM", panggil: "Panggil", status: "" },
|
||||
{ no: 4, jamPanggil: "06:47", barcode: "250826100500", noAntrian: "UM1005", originalAntrian: "UM1005", shift: "Shift 1", klinik: "MATA", fastTrack: "", pembayaran: "UMUM", panggil: "Panggil", status: "" },
|
||||
{ no: 5, jamPanggil: "06:47", barcode: "250826100525", noAntrian: "UM1006", originalAntrian: "UM1006", shift: "Shift 1", klinik: "ONKOLOGI", fastTrack: "", pembayaran: "UMUM", panggil: "Panggil", status: "" },
|
||||
{ no: 6, jamPanggil: "06:47", barcode: "250826100536", noAntrian: "UM1007", originalAntrian: "UM1007", shift: "Shift 1", klinik: "THT", fastTrack: "", pembayaran: "UMUM", panggil: "Panggil", status: "" },
|
||||
]);
|
||||
|
||||
// Tambahkan lebih banyak data pasien untuk demonstrasi "Panggil 20"
|
||||
for (let i = 7; i <= 25; i++) {
|
||||
mainPatients.value.push({
|
||||
no: i,
|
||||
jamPanggil: "07:00",
|
||||
barcode: `250826100${100 + i}`,
|
||||
noAntrian: `UM100${i}`,
|
||||
originalAntrian: `UM100${i}`,
|
||||
shift: "Shift 1",
|
||||
klinik: "UMUM",
|
||||
fastTrack: "",
|
||||
pembayaran: "UMUM",
|
||||
panggil: "Panggil",
|
||||
status: "",
|
||||
});
|
||||
}
|
||||
|
||||
const latePatients = ref([]);
|
||||
const clinicPatients = ref([]);
|
||||
const infoKlinikData = ref([
|
||||
{ no: 1, klinik: "ANESTESI", jumlahShift: "1 Shift", quotaPerShift: 1000, status: "Buka - Shift 1", dapatDiPanggil: "-", shiftBelumBuka: "-" },
|
||||
{ no: 2, klinik: "GERIATRI", jumlahShift: "1 Shift", quotaPerShift: 1000, status: "Buka - Shift 1", dapatDiPanggil: "-", shiftBelumBuka: "-" },
|
||||
{ no: 3, klinik: "GIGI DAN MULUT", jumlahShift: "1 Shift", quotaPerShift: 1000, status: "Buka - Shift 1", dapatDiPanggil: "-", shiftBelumBuka: "-" },
|
||||
{ no: 4, klinik: "HOM", jumlahShift: "1 Shift", quotaPerShift: 1000, status: "Buka - Shift 1", dapatDiPanggil: "-", shiftBelumBuka: "-" },
|
||||
{ no: 5, klinik: "IPD", jumlahShift: "1 Shift", quotaPerShift: 1000, status: "Buka - Shift 1", dapatDiPanggil: "-", shiftBelumBuka: "-" },
|
||||
{ no: 6, klinik: "JANTUNG", jumlahShift: "1 Shift", quotaPerShift: 1000, status: "Buka - Shift 1", dapatDiPanggil: "-", shiftBelumBuka: "-" },
|
||||
{ no: 7, klinik: "KANDUNGAN", jumlahShift: "1 Shift", quotaPerShift: 1000, status: "Buka - Shift 1", dapatDiPanggil: "-", shiftBelumBuka: "-" },
|
||||
{ no: 8, klinik: "KOMPLEMENTER", jumlahShift: "1 Shift", quotaPerShift: 1000, status: "Buka - Shift 1", dapatDiPanggil: "-", shiftBelumBukan: "-" },
|
||||
{ no: 9, klinik: "KUL.KEL", jumlahShift: "1 Shift", quotaPerShift: 1000, status: "Buka - Shift 1", dapatDiPanggil: "-", shiftBelumBuka: "-" },
|
||||
{ no: 10, klinik: "MATA", jumlahShift: "1 Shift", quotaPerShift: 1000, status: "Buka - Shift 1", dapatDiPanggil: "-", shiftBelumBuka: "-" },
|
||||
{ no: 11, klinik: "ONKOLOGI", jumlahShift: "1 Shift", quotaPerShift: 1000, status: "Buka - Shift 1", dapatDiPanggil: "-", shiftBelumBuka: "-" },
|
||||
{ no: 12, klinik: "PARU", jumlahShift: "1 Shift", quotaPerShift: 1000, status: "Buka - Shift 1", dapatDiPanggil: "-", shiftBelumBuka: "-" },
|
||||
{ no: 13, klinik: "SARAF", jumlahShift: "1 Shift", quotaPerShift: 1000, status: "Buka - Shift 1", dapatDiPanggil: "-", shiftBelumBuka: "-" },
|
||||
{ no: 14, klinik: "THT", jumlahShift: "1 Shift", quotaPerShift: 1000, status: "Buka - Shift 1", dapatDiPanggil: "-", shiftBelumBuka: "-" },
|
||||
]);
|
||||
|
||||
// Computed properties for totals
|
||||
const totalDapatDipanggil = computed(() => {
|
||||
return infoKlinikData.value.reduce((total, item) => {
|
||||
return total + (item.dapatDiPanggil === "-" ? 0 : parseInt(item.dapatDiPanggil));
|
||||
}, 0);
|
||||
});
|
||||
|
||||
const totalShiftBelumBuka = computed(() => {
|
||||
return infoKlinikData.value.reduce((total, item) => {
|
||||
return total + (item.shiftBelumBuka === "-" ? 0 : parseInt(item.shiftBelumBuka));
|
||||
}, 0);
|
||||
});
|
||||
|
||||
// Methods
|
||||
const getRowClass = (item) => {
|
||||
if (item.status === 'dipanggil') {
|
||||
return 'called-row';
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
const handleStatusCardClick = (count) => {
|
||||
console.log(`Memanggil ${count} antrean pasien.`);
|
||||
const updatedPatients = mainPatients.value.map((patient, index) => {
|
||||
const isCalled = index < count;
|
||||
return {
|
||||
...patient,
|
||||
status: isCalled ? 'dipanggil' : '',
|
||||
noAntrian: isCalled ? `${patient.originalAntrian} | Online - ${patient.barcode}` : patient.originalAntrian
|
||||
};
|
||||
});
|
||||
mainPatients.value = updatedPatients;
|
||||
};
|
||||
|
||||
const handleNextPatientClick = () => {
|
||||
console.log("Tombol Panggil Pasien Selanjutnya diklik! (Mengambil pasien pertama dari antrean)");
|
||||
const updatedPatients = mainPatients.value.map(patient => {
|
||||
return { ...patient, status: '', noAntrian: patient.originalAntrian };
|
||||
});
|
||||
mainPatients.value = updatedPatients;
|
||||
};
|
||||
|
||||
const handlePanggilClick = (item) => {
|
||||
console.log(`Tombol Panggil untuk pasien: ${item.noAntrian} diklik!`);
|
||||
|
||||
// Membuat salinan baru dari seluruh array untuk memicu reaktivitas
|
||||
const updatedPatients = mainPatients.value.map(p => {
|
||||
// Jika pasien cocok, buat salinan baru dengan status 'dipanggil' dan tambahkan "Online"
|
||||
if (p.no === item.no) {
|
||||
return {
|
||||
...p,
|
||||
status: 'dipanggil',
|
||||
noAntrian: `${p.originalAntrian} | Online - ${p.barcode}`
|
||||
};
|
||||
}
|
||||
// Jika tidak, kembalikan objek pasien aslinya
|
||||
return p;
|
||||
});
|
||||
|
||||
// Ganti seluruh array data dengan salinan yang baru.
|
||||
mainPatients.value = updatedPatients;
|
||||
};
|
||||
|
||||
const handleProsesClick = (item) => {
|
||||
console.log(`Tombol Proses untuk pasien: ${item.noAntrian} diklik!`);
|
||||
|
||||
const updatedPatients = mainPatients.value.map(p => {
|
||||
if (p.no === item.no) {
|
||||
return {
|
||||
...p,
|
||||
status: 'dalam_proses'
|
||||
};
|
||||
}
|
||||
return p;
|
||||
});
|
||||
|
||||
mainPatients.value = updatedPatients;
|
||||
};
|
||||
|
||||
// Mengatur tanggal saat komponen dimuat
|
||||
onMounted(() => {
|
||||
const today = new Date();
|
||||
const optionsLong = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' };
|
||||
currentDateLongFormatted.value = today.toLocaleDateString('id-ID', optionsLong);
|
||||
|
||||
const optionsShort = { year: 'numeric', month: 'long', day: 'numeric' };
|
||||
currentDateShortFormatted.value = today.toLocaleDateString('id-ID', optionsShort);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* Scoped styles for a cleaner look */
|
||||
.main-content-padding {
|
||||
padding-left: 24px !important;
|
||||
padding-right: 24px !important;
|
||||
}
|
||||
|
||||
/* Header Banner */
|
||||
.header-banner {
|
||||
background: linear-gradient(90deg, #1565C0, #1976D2);
|
||||
color: white;
|
||||
min-height: 120px;
|
||||
}
|
||||
|
||||
/* Next Patient Card */
|
||||
.next-patient-card {
|
||||
background: linear-gradient(45deg, #00A896, #00796B);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Status Cards */
|
||||
.v-card.text-center {
|
||||
transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
|
||||
cursor: pointer;
|
||||
}
|
||||
.v-card.text-center:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
/* Table styling */
|
||||
.custom-table :deep(thead th) {
|
||||
background-color: #E8EAF6; /* Light gray-blue header */
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.custom-table :deep(tbody tr:nth-of-type(odd)) {
|
||||
background-color: #F5F5F5; /* Light gray for odd rows */
|
||||
}
|
||||
|
||||
/* Highlighted row for "dipanggil" status */
|
||||
.custom-table :deep(tbody tr.called-row) {
|
||||
background-color: #998479 !important; /* Light green background */
|
||||
}
|
||||
|
||||
/* Field and Select styling */
|
||||
.select-items .v-field--variant-solo,
|
||||
.v-text-field .v-field--variant-solo {
|
||||
background-color: #ECEFF1;
|
||||
}
|
||||
|
||||
.text-blue {
|
||||
color: #1976D2 !important;
|
||||
}
|
||||
|
||||
.text-primary {
|
||||
color: #1976D2 !important;
|
||||
}
|
||||
|
||||
.online-antrian {
|
||||
font-weight: bold;
|
||||
color: #1976D2;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,491 @@
|
||||
<template>
|
||||
<div class="master-klinik-page pa-4">
|
||||
<!-- Breadcrumbs -->
|
||||
<v-breadcrumbs :items="breadcrumbs" class="pl-0">
|
||||
<template v-slot:divider>
|
||||
<v-icon icon="mdi-chevron-right"></v-icon>
|
||||
</template>
|
||||
</v-breadcrumbs>
|
||||
|
||||
<!-- Tampilan Formulir Tambah/Edit/View -->
|
||||
<v-card v-if="showForm" class="pa-4 rounded-lg elevation-2">
|
||||
<v-card-title class="text-h5 font-weight-bold mb-4">
|
||||
{{ isEditMode ? 'Edit Klinik' : readOnly ? 'Detail Klinik' : 'Tambah Klinik' }}
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-row>
|
||||
<v-col cols="12" md="6">
|
||||
<v-label class="font-weight-bold">Kode</v-label>
|
||||
<v-text-field
|
||||
v-model="editedItem.kode"
|
||||
placeholder="Masukkan Kode Klinik"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
class="mb-2"
|
||||
:readonly="readOnly"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-label class="font-weight-bold">Nama</v-label>
|
||||
<v-text-field
|
||||
v-model="editedItem.namaKlinik"
|
||||
placeholder="Masukkan Nama Klinik"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
class="mb-2"
|
||||
:readonly="readOnly"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col cols="12" md="6">
|
||||
<v-label class="font-weight-bold">Shift</v-label>
|
||||
<v-text-field
|
||||
v-model="editedItem.shift"
|
||||
placeholder="Jumlah Shift"
|
||||
type="number"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
class="mb-2"
|
||||
:readonly="readOnly"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-label class="font-weight-bold">Kuota Shift</v-label>
|
||||
<v-text-field
|
||||
v-model="editedItem.kuotaShift"
|
||||
placeholder="Kuota Per Shift"
|
||||
type="number"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
class="mb-2"
|
||||
:readonly="readOnly"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row class="d-flex align-center">
|
||||
<v-col cols="12" md="6">
|
||||
<v-label class="font-weight-bold">Jam Buka Shift</v-label>
|
||||
<div class="d-flex align-center">
|
||||
<v-text-field
|
||||
v-model="editedItem.jamBuka"
|
||||
placeholder="Jam Buka Shift 1"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
class="mr-2"
|
||||
:readonly="readOnly"
|
||||
></v-text-field>
|
||||
<span class="text-h6 font-weight-bold mr-2">:</span>
|
||||
<v-text-field
|
||||
v-model="editedItem.menitBuka"
|
||||
placeholder="Menit Buka Shift 1"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
:readonly="readOnly"
|
||||
></v-text-field>
|
||||
</div>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-switch
|
||||
v-model="editedItem.autoShift"
|
||||
inset
|
||||
color="primary"
|
||||
label="Auto Shift"
|
||||
hide-details
|
||||
:readonly="readOnly"
|
||||
></v-switch>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-label class="font-weight-bold">Kuota Bangku</v-label>
|
||||
<v-text-field
|
||||
v-model="editedItem.kuotaBangku"
|
||||
placeholder="Masukkan Kuota Bangku Klinik"
|
||||
type="number"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
class="mb-2"
|
||||
:readonly="readOnly"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-label class="font-weight-bold">Jadwal Klinik</v-label>
|
||||
<v-table class="rounded-lg elevation-1 mt-2">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-left text-uppercase font-weight-bold">#</th>
|
||||
<th class="text-left text-uppercase font-weight-bold">Hari</th>
|
||||
<th class="text-left text-uppercase font-weight-bold">Pilih</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(day, index) in days" :key="index">
|
||||
<td>{{ index + 1 }}</td>
|
||||
<td>{{ day.name }}</td>
|
||||
<td>
|
||||
<v-checkbox
|
||||
v-model="day.selected"
|
||||
color="primary"
|
||||
hide-details
|
||||
:readonly="readOnly"
|
||||
></v-checkbox>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</v-table>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
<v-card-actions class="d-flex justify-start pa-4">
|
||||
<v-btn
|
||||
color="grey-darken-1"
|
||||
variant="flat"
|
||||
class="text-capitalize rounded-lg mr-2"
|
||||
@click="cancelForm"
|
||||
>
|
||||
{{ readOnly ? 'Tutup' : 'Batal' }}
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-if="!readOnly"
|
||||
color="orange-darken-2"
|
||||
variant="flat"
|
||||
class="text-capitalize rounded-lg"
|
||||
@click="saveItem"
|
||||
>
|
||||
Submit
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
|
||||
<!-- Tampilan Tabel Data -->
|
||||
<div v-else>
|
||||
<!-- Banner biru sebagai pengganti header h1 -->
|
||||
<v-card class="d-flex justify-space-between align-center pa-4 mb-4 rounded-lg bg-blue-darken-2 text-white elevation-2">
|
||||
<v-card-title class="d-flex align-center text-h5 font-weight-bold pa-0">
|
||||
<v-icon icon="mdi-hospital" class="mr-2" size="40"></v-icon>
|
||||
<span>Master Klinik</span>
|
||||
</v-card-title>
|
||||
<v-btn
|
||||
color="success"
|
||||
prepend-icon="mdi-plus"
|
||||
rounded
|
||||
class="text-capitalize"
|
||||
@click="showForm = true"
|
||||
>
|
||||
Tambah Baru
|
||||
</v-btn>
|
||||
</v-card>
|
||||
|
||||
<v-card class="pa-4 rounded-lg elevation-2">
|
||||
<v-card-text>
|
||||
<!-- Table controls -->
|
||||
<div class="d-flex flex-wrap align-center justify-space-between mb-4">
|
||||
<div class="d-flex align-center">
|
||||
<span class="mr-2 text-subtitle-1">Show</span>
|
||||
<v-select
|
||||
v-model="itemsPerPage"
|
||||
:items="[10, 25, 50]"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
hide-details
|
||||
style="max-width: 80px;"
|
||||
rounded
|
||||
class="mr-2"
|
||||
></v-select>
|
||||
<span class="text-subtitle-1">entries</span>
|
||||
</div>
|
||||
<div class="d-flex align-center">
|
||||
<v-text-field
|
||||
v-model="search"
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
label="Search"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
hide-details
|
||||
rounded
|
||||
clearable
|
||||
></v-text-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Data Table dengan paginasi kustom -->
|
||||
<v-data-table
|
||||
:headers="headers"
|
||||
:items="allKlinikData"
|
||||
:search="search"
|
||||
:items-per-page="itemsPerPage"
|
||||
v-model:page="page"
|
||||
class="rounded-lg elevation-0 custom-table"
|
||||
>
|
||||
<!-- Slot untuk aksi di setiap baris -->
|
||||
<template v-slot:[`item.actions`]="{ item }">
|
||||
<!-- Tombol "View" yang hanya menampilkan detail -->
|
||||
<v-btn icon color="blue" size="small" class="mr-2" @click="viewItem(item)">
|
||||
<v-icon>mdi-eye</v-icon>
|
||||
</v-btn>
|
||||
<v-btn icon color="orange" size="small" class="mr-2" @click="editItem(item)">
|
||||
<v-icon>mdi-pencil</v-icon>
|
||||
</v-btn>
|
||||
<v-btn icon color="red" size="small" @click="deleteItem(item)">
|
||||
<v-icon>mdi-delete</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
|
||||
<template v-slot:no-data>
|
||||
<v-alert :value="true" color="grey-lighten-3" icon="mdi-information">
|
||||
Tidak ada data yang tersedia.
|
||||
</v-alert>
|
||||
</template>
|
||||
|
||||
<!-- Slot kustom untuk footer tabel (paginasi) -->
|
||||
<template v-slot:bottom>
|
||||
<v-row class="ma-2">
|
||||
<v-col cols="12" sm="6" class="d-flex align-center justify-start text-caption text-grey">
|
||||
{{ showingEntriesText }}
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" class="d-flex align-center justify-end">
|
||||
<v-pagination
|
||||
v-model="page"
|
||||
:length="pageCount"
|
||||
rounded="circle"
|
||||
:total-visible="5"
|
||||
></v-pagination>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Delete Dialog -->
|
||||
<v-dialog v-model="showDeleteDialog" max-width="400px">
|
||||
<v-card class="pa-4 rounded-lg">
|
||||
<v-card-title class="text-h6 font-weight-bold">Hapus Data</v-card-title>
|
||||
<v-card-text>Apakah Anda yakin ingin menghapus data ini?</v-card-text>
|
||||
<v-card-actions class="d-flex justify-end">
|
||||
<v-btn color="grey-darken-1" variant="text" @click="closeDeleteDialog">Batal</v-btn>
|
||||
<v-btn color="red" variant="text" @click="confirmDelete">Hapus</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
|
||||
definePageMeta({
|
||||
middleware:['auth']
|
||||
})
|
||||
|
||||
// State untuk menampilkan/menyembunyikan formulir
|
||||
const showForm = ref(false);
|
||||
const readOnly = ref(false); // State untuk mode "view"
|
||||
|
||||
// Data dummy untuk Master Klinik
|
||||
const allKlinikData = ref([
|
||||
{ id: 1, kode: 'AN', namaKlinik: 'ANAK', shift: 1, kuotaShift: 1000, kuotaBangku: 50, jamBuka: '08', menitBuka: '00', autoShift: true, jadwal: [] },
|
||||
{ id: 2, kode: 'AS', namaKlinik: 'ANESTESI', shift: 1, kuotaShift: 1000, kuotaBangku: 50, jamBuka: '08', menitBuka: '00', autoShift: true, jadwal: [] },
|
||||
{ id: 3, kode: 'BD', namaKlinik: 'BEDAH', shift: 1, kuotaShift: 1000, kuotaBangku: 50, jamBuka: '08', menitBuka: '00', autoShift: true, jadwal: [] },
|
||||
{ id: 4, kode: 'GR', namaKlinik: 'GERIATRI', shift: 1, kuotaShift: 1000, kuotaBangku: 50, jamBuka: '08', menitBuka: '00', autoShift: true, jadwal: [] },
|
||||
{ id: 5, kode: 'GI', namaKlinik: 'GIGI DAN MULUT', shift: 1, kuotaShift: 1000, kuotaBangku: 50, jamBuka: '08', menitBuka: '00', autoShift: true, jadwal: [] },
|
||||
{ id: 6, kode: 'GZ', namaKlinik: 'GIZI', shift: 1, kuotaShift: 1000, kuotaBangku: 50, jamBuka: '08', menitBuka: '00', autoShift: true, jadwal: [] },
|
||||
{ id: 7, kode: 'HO', namaKlinik: 'HOM', shift: 1, kuotaShift: 1000, kuotaBangku: 50, jamBuka: '08', menitBuka: '00', autoShift: true, jadwal: [] },
|
||||
{ id: 8, kode: 'IP', namaKlinik: 'IPD', shift: 1, kuotaShift: 1000, kuotaBangku: 50, jamBuka: '08', menitBuka: '00', autoShift: true, jadwal: [] },
|
||||
{ id: 9, kode: 'JT', namaKlinik: 'JANTUNG', shift: 1, kuotaShift: 1000, kuotaBangku: 50, jamBuka: '08', menitBuka: '00', autoShift: true, jadwal: [] },
|
||||
{ id: 10, kode: 'JW', namaKlinik: 'JIWA', shift: 1, kuotaShift: 1000, kuotaBangku: 50, jamBuka: '08', menitBuka: '00', autoShift: true, jadwal: [] },
|
||||
{ id: 11, kode: 'OB', namaKlinik: 'KANDUNGAN', shift: 1, kuotaShift: 1000, kuotaBangku: 50, jamBuka: '08', menitBuka: '00', autoShift: true, jadwal: [] },
|
||||
{ id: 12, kode: 'KT', namaKlinik: 'KEMOTERAPI', shift: 1, kuotaShift: 1000, kuotaBangku: 50, jamBuka: '08', menitBuka: '00', autoShift: true, jadwal: [] },
|
||||
{ id: 13, kode: 'KN', namaKlinik: 'KOMPLEMENTER', shift: 1, kuotaShift: 1000, kuotaBangku: 50, jamBuka: '08', menitBuka: '00', autoShift: true, jadwal: [] },
|
||||
{ id: 14, kode: 'KL', namaKlinik: 'KUL KEL', shift: 1, kuotaShift: 1000, kuotaBangku: 50, jamBuka: '08', menitBuka: '00', autoShift: true, jadwal: [] },
|
||||
{ id: 15, kode: 'MT', namaKlinik: 'MATA', shift: 1, kuotaShift: 1000, kuotaBangku: 50, jamBuka: '08', menitBuka: '00', autoShift: true, jadwal: [] },
|
||||
{ id: 16, kode: 'MC', namaKlinik: 'MCU', shift: 1, kuotaShift: 1000, kuotaBangku: 50, jamBuka: '08', menitBuka: '00', autoShift: true, jadwal: [] },
|
||||
{ id: 17, kode: 'ON', namaKlinik: 'ONKOLOGI', shift: 1, kuotaShift: 1000, kuotaBangku: 50, jamBuka: '08', menitBuka: '00', autoShift: true, jadwal: [] },
|
||||
{ id: 18, kode: 'PR', namaKlinik: 'PARU', shift: 1, kuotaShift: 1000, kuotaBangku: 50, jamBuka: '08', menitBuka: '00', autoShift: true, jadwal: [] },
|
||||
{ id: 19, kode: 'RT', namaKlinik: 'R. TINDAKAN', shift: 1, kuotaShift: 1000, kuotaBangku: 50, jamBuka: '08', menitBuka: '00', autoShift: true, jadwal: [] },
|
||||
{ id: 20, kode: 'RD', namaKlinik: 'RADIOTERAPI', shift: 1, kuotaShift: 1000, kuotaBangku: 50, jamBuka: '08', menitBuka: '00', autoShift: true, jadwal: [] },
|
||||
{ id: 21, kode: 'RM', namaKlinik: 'REHAB MEDIK', shift: 1, kuotaShift: 1000, kuotaBangku: 50, jamBuka: '08', menitBuka: '00', autoShift: true, jadwal: [] },
|
||||
{ id: 22, kode: 'NU', namaKlinik: 'SARAF', shift: 1, kuotaShift: 1000, kuotaBangku: 50, jamBuka: '08', menitBuka: '00', autoShift: true, jadwal: [] },
|
||||
{ id: 23, kode: 'TH', namaKlinik: 'THT', shift: 1, kuotaShift: 1000, kuotaBangku: 50, jamBuka: '08', menitBuka: '00', autoShift: true, jadwal: [] },
|
||||
]);
|
||||
|
||||
const headers = ref([
|
||||
{ title: 'No', key: 'id' },
|
||||
{ title: 'Kode', key: 'kode', sortable: true },
|
||||
{ title: 'Nama Klinik', key: 'namaKlinik', sortable: true },
|
||||
{ title: 'Shift', key: 'shift', sortable: true },
|
||||
{ title: 'Kuota Shift', key: 'kuotaShift', sortable: true },
|
||||
{ title: 'Aksi', key: 'actions', sortable: false },
|
||||
]);
|
||||
|
||||
// Breadcrumbs
|
||||
const breadcrumbs = ref([
|
||||
{ title: 'Dashboard', disabled: false, href: '/dashboard' },
|
||||
{ title: 'Setting', disabled: false, href: '/setting' },
|
||||
{ title: 'Master Klinik', disabled: false, href: '/setting/masterklinik' },
|
||||
]);
|
||||
|
||||
const days = ref([
|
||||
{ name: 'Senin', selected: false },
|
||||
{ name: 'Selasa', selected: false },
|
||||
{ name: 'Rabu', selected: false },
|
||||
{ name: 'Kamis', selected: false },
|
||||
{ name: 'Jum`at', selected: false },
|
||||
]);
|
||||
|
||||
// State untuk tabel dan paginasi
|
||||
const itemsPerPage = ref(10);
|
||||
const search = ref('');
|
||||
const page = ref(1);
|
||||
|
||||
// Computed properties untuk paginasi kustom
|
||||
const pageCount = computed(() => {
|
||||
return Math.ceil(allKlinikData.value.length / itemsPerPage.value);
|
||||
});
|
||||
|
||||
const showingEntriesText = computed(() => {
|
||||
const start = (page.value - 1) * itemsPerPage.value + 1;
|
||||
const end = Math.min(page.value * itemsPerPage.value, allKlinikData.value.length);
|
||||
const total = allKlinikData.value.length;
|
||||
return `Showing ${start} to ${end} of ${total} entries`;
|
||||
});
|
||||
|
||||
// State untuk dialog
|
||||
const showDeleteDialog = ref(false);
|
||||
const isEditMode = ref(false);
|
||||
const editedIndex = ref(-1);
|
||||
const editedItem = ref({
|
||||
id: 0,
|
||||
kode: '',
|
||||
namaKlinik: '',
|
||||
shift: 1,
|
||||
kuotaShift: 0,
|
||||
kuotaBangku: 0,
|
||||
jamBuka: '',
|
||||
menitBuka: '',
|
||||
autoShift: false,
|
||||
});
|
||||
|
||||
|
||||
// Fungsi untuk tombol aksi
|
||||
// Fungsi untuk tombol View yang hanya menampilkan data tanpa bisa diedit
|
||||
const viewItem = (item) => {
|
||||
editedItem.value = { ...item };
|
||||
readOnly.value = true;
|
||||
isEditMode.value = false;
|
||||
showForm.value = true;
|
||||
// Update breadcrumbs untuk mode view
|
||||
breadcrumbs.value = [
|
||||
{ title: 'Dashboard', disabled: false, href: '/dashboard' },
|
||||
{ title: 'Setting', disabled: false, href: '/setting' },
|
||||
{ title: 'Master Klinik', disabled: false, href: '/setting/masterklinik' },
|
||||
{ title: 'Detail Klinik', disabled: true, href: '/setting/viewklinik' },
|
||||
];
|
||||
};
|
||||
|
||||
const editItem = (item) => {
|
||||
editedIndex.value = allKlinikData.value.findIndex(d => d.id === item.id);
|
||||
editedItem.value = { ...item };
|
||||
isEditMode.value = true;
|
||||
readOnly.value = false;
|
||||
showForm.value = true;
|
||||
// Update breadcrumbs untuk mode edit
|
||||
breadcrumbs.value = [
|
||||
{ title: 'Dashboard', disabled: false, href: '/dashboard' },
|
||||
{ title: 'Setting', disabled: false, href: '/setting' },
|
||||
{ title: 'Master Klinik', disabled: false, href: '/setting/masterklinik' },
|
||||
{ title: 'Edit Klinik', disabled: true, href: '/setting/editklinik' },
|
||||
];
|
||||
};
|
||||
|
||||
const deleteItem = (item) => {
|
||||
editedIndex.value = allKlinikData.value.findIndex(d => d.id === item.id);
|
||||
showDeleteDialog.value = true;
|
||||
};
|
||||
|
||||
const confirmDelete = () => {
|
||||
if (editedIndex.value > -1) {
|
||||
allKlinikData.value.splice(editedIndex.value, 1);
|
||||
}
|
||||
closeDeleteDialog();
|
||||
};
|
||||
|
||||
const cancelForm = () => {
|
||||
showForm.value = false;
|
||||
isEditMode.value = false;
|
||||
readOnly.value = false;
|
||||
editedItem.value = {
|
||||
id: 0,
|
||||
kode: '',
|
||||
namaKlinik: '',
|
||||
shift: 1,
|
||||
kuotaShift: 0,
|
||||
kuotaBangku: 0,
|
||||
jamBuka: '',
|
||||
menitBuka: '',
|
||||
autoShift: false,
|
||||
};
|
||||
editedIndex.value = -1;
|
||||
// Reset breadcrumbs ke mode tabel
|
||||
breadcrumbs.value = [
|
||||
{ title: 'Dashboard', disabled: false, href: '/dashboard' },
|
||||
{ title: 'Setting', disabled: false, href: '/setting' },
|
||||
{ title: 'Master Klinik', disabled: false, href: '/setting/masterklinik' },
|
||||
];
|
||||
};
|
||||
|
||||
const closeDeleteDialog = () => {
|
||||
showDeleteDialog.value = false;
|
||||
editedIndex.value = -1;
|
||||
};
|
||||
|
||||
const saveItem = () => {
|
||||
if (isEditMode.value) {
|
||||
Object.assign(allKlinikData.value[editedIndex.value], editedItem.value);
|
||||
} else {
|
||||
// Generate new ID
|
||||
const newId = allKlinikData.value.length > 0 ? Math.max(...allKlinikData.value.map(item => item.id)) + 1 : 1;
|
||||
editedItem.value.id = newId;
|
||||
allKlinikData.value.push(editedItem.value);
|
||||
}
|
||||
cancelForm(); // Kembali ke tampilan tabel setelah menyimpan
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.master-klinik-page {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.custom-table {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.v-data-table :deep(th) {
|
||||
font-weight: bold !important;
|
||||
color: #333 !important;
|
||||
}
|
||||
|
||||
.v-data-table :deep(td) {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.v-btn--icon {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.v-select :deep(.v-field__input) {
|
||||
padding-top: 0 !important;
|
||||
padding-bottom: 0 !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,376 @@
|
||||
<template>
|
||||
<div class="master-klinik-ruang-page pa-4">
|
||||
<!-- Breadcrumbs -->
|
||||
<v-breadcrumbs :items="breadcrumbs" class="pl-0">
|
||||
<template v-slot:divider>
|
||||
<v-icon icon="mdi-chevron-right"></v-icon>
|
||||
</template>
|
||||
</v-breadcrumbs>
|
||||
|
||||
<!-- Tampilan Formulir Tambah/Edit/View -->
|
||||
<v-card v-if="showForm" class="pa-4 rounded-lg elevation-2">
|
||||
<v-card-title class="text-h5 font-weight-bold mb-4">
|
||||
{{ isEditMode ? 'Edit Ruang Klinik' : readOnly ? 'Detail Ruang Klinik' : 'Tambah Ruang Klinik' }}
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-row>
|
||||
<v-col cols="12" md="6">
|
||||
<v-label class="font-weight-bold">Nama Klinik</v-label>
|
||||
<v-select
|
||||
v-model="editedItem.namaKlinik"
|
||||
:items="['ANAK', 'ANESTESI', 'BEDAH', 'GERIATRI', 'GIGI DAN MULUT', 'GIZI', 'HOM', 'IPD', 'JANTUNG', 'JIWA', 'KANDUNGAN', 'KEMOTERAPI', 'KOMPLEMENTER', 'KUL KEL', 'MATA', 'MCU', 'ONKOLOGI', 'PARU', 'R. TINDAKAN', 'RADIOTERAPI', 'REHAB MEDIK', 'SARAF', 'THT']"
|
||||
placeholder="Pilih Nama Klinik"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
class="mb-2"
|
||||
:readonly="readOnly"
|
||||
></v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-label class="font-weight-bold">Kode Ruang</v-label>
|
||||
<v-text-field
|
||||
v-model="editedItem.kode"
|
||||
placeholder="Masukkan Kode Ruang"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
class="mb-2"
|
||||
:readonly="readOnly"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-label class="font-weight-bold">Nama Ruang</v-label>
|
||||
<v-text-field
|
||||
v-model="editedItem.namaRuang"
|
||||
placeholder="Masukkan Nama Ruang"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
class="mb-2"
|
||||
:readonly="readOnly"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
<v-card-actions class="d-flex justify-start pa-4">
|
||||
<v-btn
|
||||
color="grey-darken-1"
|
||||
variant="flat"
|
||||
class="text-capitalize rounded-lg mr-2"
|
||||
@click="cancelForm"
|
||||
>
|
||||
{{ readOnly ? 'Tutup' : 'Batal' }}
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-if="!readOnly"
|
||||
color="orange-darken-2"
|
||||
variant="flat"
|
||||
class="text-capitalize rounded-lg"
|
||||
@click="saveItem"
|
||||
>
|
||||
Submit
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
|
||||
<!-- Tampilan Tabel Data -->
|
||||
<div v-else>
|
||||
<!-- Banner biru sebagai pengganti header h1 -->
|
||||
<v-card class="d-flex justify-space-between align-center pa-4 mb-4 rounded-lg bg-blue-darken-2 text-white elevation-2">
|
||||
<v-card-title class="d-flex align-center text-h5 font-weight-bold pa-0">
|
||||
<v-icon icon="mdi-hospital-box-outline" class="mr-2" size="40"></v-icon>
|
||||
<span>Master Klinik Ruang</span>
|
||||
</v-card-title>
|
||||
<v-btn
|
||||
color="success"
|
||||
prepend-icon="mdi-plus"
|
||||
rounded
|
||||
class="text-capitalize"
|
||||
@click="showForm = true"
|
||||
>
|
||||
Tambah Baru
|
||||
</v-btn>
|
||||
</v-card>
|
||||
|
||||
<v-card class="pa-4 rounded-lg elevation-2">
|
||||
<v-card-text>
|
||||
<!-- Table controls -->
|
||||
<div class="d-flex flex-wrap align-center justify-space-between mb-4">
|
||||
<div class="d-flex align-center">
|
||||
<span class="mr-2 text-subtitle-1">Show</span>
|
||||
<v-select
|
||||
v-model="itemsPerPage"
|
||||
:items="[10, 25, 50]"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
hide-details
|
||||
style="max-width: 80px;"
|
||||
rounded
|
||||
class="mr-2"
|
||||
></v-select>
|
||||
<span class="text-subtitle-1">entries</span>
|
||||
</div>
|
||||
<div class="d-flex align-center">
|
||||
<v-text-field
|
||||
v-model="search"
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
label="Search"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
hide-details
|
||||
rounded
|
||||
clearable
|
||||
></v-text-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Data Table dengan paginasi kustom -->
|
||||
<v-data-table
|
||||
:headers="headers"
|
||||
:items="allRuangData"
|
||||
:search="search"
|
||||
:items-per-page="itemsPerPage"
|
||||
v-model:page="page"
|
||||
class="rounded-lg elevation-0 custom-table"
|
||||
>
|
||||
<!-- Slot untuk aksi di setiap baris -->
|
||||
<template v-slot:[`item.actions`]="{ item }">
|
||||
<v-btn icon color="blue" size="small" class="mr-2" @click="viewItem(item)">
|
||||
<v-icon>mdi-eye</v-icon>
|
||||
</v-btn>
|
||||
<v-btn icon color="orange" size="small" class="mr-2" @click="editItem(item)">
|
||||
<v-icon>mdi-pencil</v-icon>
|
||||
</v-btn>
|
||||
<v-btn icon color="red" size="small" @click="deleteItem(item)">
|
||||
<v-icon>mdi-delete</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
|
||||
<template v-slot:no-data>
|
||||
<v-alert :value="true" color="grey-lighten-3" icon="mdi-information">
|
||||
Tidak ada data yang tersedia.
|
||||
</v-alert>
|
||||
</template>
|
||||
|
||||
<!-- Slot kustom untuk footer tabel (paginasi) -->
|
||||
<template v-slot:bottom>
|
||||
<v-row class="ma-2">
|
||||
<v-col cols="12" sm="6" class="d-flex align-center justify-start text-caption text-grey">
|
||||
{{ showingEntriesText }}
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" class="d-flex align-center justify-end">
|
||||
<v-pagination
|
||||
v-model="page"
|
||||
:length="pageCount"
|
||||
rounded="circle"
|
||||
:total-visible="5"
|
||||
></v-pagination>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Delete Dialog -->
|
||||
<v-dialog v-model="showDeleteDialog" max-width="400px">
|
||||
<v-card class="pa-4 rounded-lg">
|
||||
<v-card-title class="text-h6 font-weight-bold">Hapus Data</v-card-title>
|
||||
<v-card-text>Apakah Anda yakin ingin menghapus data ini?</v-card-text>
|
||||
<v-card-actions class="d-flex justify-end">
|
||||
<v-btn color="grey-darken-1" variant="text" @click="closeDeleteDialog">Batal</v-btn>
|
||||
<v-btn color="red" variant="text" @click="confirmDelete">Hapus</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
|
||||
definePageMeta({
|
||||
middleware:['auth']
|
||||
})
|
||||
|
||||
// State untuk menampilkan/menyembunyikan formulir
|
||||
const showForm = ref(false);
|
||||
const readOnly = ref(false);
|
||||
|
||||
// Data dummy untuk Master Klinik Ruang
|
||||
const allRuangData = ref([
|
||||
{ id: 1, namaKlinik: 'ANAK', kode: 'AN-01', namaRuang: 'RUANG ANAK 1' },
|
||||
{ id: 2, namaKlinik: 'ANAK', kode: 'AN-02', namaRuang: 'RUANG ANAK 2' },
|
||||
{ id: 3, namaKlinik: 'ANESTESI', kode: 'AS-01', namaRuang: 'RUANG ANESTESI 1' },
|
||||
{ id: 4, namaKlinik: 'BEDAH', kode: 'BD-01', namaRuang: 'RUANG BEDAH 1' },
|
||||
{ id: 5, namaKlinik: 'GIGI DAN MULUT', kode: 'GI-01', namaRuang: 'RUANG GIGI DAN MULUT 1' },
|
||||
{ id: 6, namaKlinik: 'GERIATRI', kode: 'GR-01', namaRuang: 'RUANG GERIATRI 1' },
|
||||
{ id: 7, namaKlinik: 'GIZI', kode: 'GZ-01', namaRuang: 'RUANG GIZI 1' },
|
||||
{ id: 8, namaKlinik: 'HOM', kode: 'HO-01', namaRuang: 'RUANG HOM 1' },
|
||||
{ id: 9, namaKlinik: 'IPD', kode: 'IP-01', namaRuang: 'RUANG IPD 1' },
|
||||
{ id: 10, namaKlinik: 'JANTUNG', kode: 'JT-01', namaRuang: 'RUANG JANTUNG 1' },
|
||||
{ id: 11, namaKlinik: 'JIWA', kode: 'JW-01', namaRuang: 'RUANG JIWA 1' },
|
||||
{ id: 12, namaKlinik: 'KANDUNGAN', kode: 'OB-01', namaRuang: 'RUANG KANDUNGAN 1' },
|
||||
{ id: 13, namaKlinik: 'KANDUNGAN', kode: 'OB-02', namaRuang: 'RUANG KANDUNGAN 2' },
|
||||
{ id: 14, namaKlinik: 'KEMOTERAPI', kode: 'KT-01', namaRuang: 'RUANG KEMOTERAPI 1' },
|
||||
{ id: 15, namaKlinik: 'KOMPLEMENTER', kode: 'KN-01', namaRuang: 'RUANG KOMPLEMENTER 1' },
|
||||
{ id: 16, namaKlinik: 'KUL KEL', kode: 'KL-01', namaRuang: 'RUANG KUL KEL 1' },
|
||||
{ id: 17, namaKlinik: 'MATA', kode: 'MT-01', namaRuang: 'RUANG MATA 1' },
|
||||
{ id: 18, namaKlinik: 'MCU', kode: 'MC-01', namaRuang: 'RUANG MCU 1' },
|
||||
{ id: 19, namaKlinik: 'ONKOLOGI', kode: 'ON-01', namaRuang: 'RUANG ONKOLOGI 1' },
|
||||
{ id: 20, namaKlinik: 'PARU', kode: 'PR-01', namaRuang: 'RUANG PARU 1' },
|
||||
{ id: 21, namaKlinik: 'R. TINDAKAN', kode: 'RT-01', namaRuang: 'RUANG R. TINDAKAN 1' },
|
||||
{ id: 22, namaKlinik: 'RADIOTERAPI', kode: 'RD-01', namaRuang: 'RUANG RADIOTERAPI 1' },
|
||||
{ id: 23, namaKlinik: 'REHAB MEDIK', kode: 'RM-01', namaRuang: 'RUANG REHAB MEDIK 1' },
|
||||
{ id: 24, namaKlinik: 'SARAF', kode: 'NU-01', namaRuang: 'RUANG SARAF 1' },
|
||||
{ id: 25, namaKlinik: 'THT', kode: 'TH-01', namaRuang: 'RUANG THT 1' },
|
||||
]);
|
||||
|
||||
const headers = ref([
|
||||
{ title: 'No', key: 'id' },
|
||||
{ title: 'Nama Klinik', key: 'namaKlinik', sortable: true },
|
||||
{ title: 'Kode Ruang', key: 'kode', sortable: true },
|
||||
{ title: 'Nama Ruang', key: 'namaRuang', sortable: true },
|
||||
{ title: 'Aksi', key: 'actions', sortable: false },
|
||||
]);
|
||||
|
||||
// Breadcrumbs
|
||||
const breadcrumbs = ref([
|
||||
{ title: 'Dashboard', disabled: false, href: '/dashboard' },
|
||||
{ title: 'Setting', disabled: false, href: '/setting' },
|
||||
{ title: 'Master Klinik Ruang', disabled: false, href: '/setting/masterklinikruang' },
|
||||
]);
|
||||
|
||||
// State untuk tabel dan paginasi
|
||||
const itemsPerPage = ref(10);
|
||||
const search = ref('');
|
||||
const page = ref(1);
|
||||
|
||||
// Computed properties untuk paginasi kustom
|
||||
const pageCount = computed(() => {
|
||||
return Math.ceil(allRuangData.value.length / itemsPerPage.value);
|
||||
});
|
||||
|
||||
const showingEntriesText = computed(() => {
|
||||
const start = (page.value - 1) * itemsPerPage.value + 1;
|
||||
const end = Math.min(page.value * itemsPerPage.value, allRuangData.value.length);
|
||||
const total = allRuangData.value.length;
|
||||
return `Showing ${start} to ${end} of ${total} entries`;
|
||||
});
|
||||
|
||||
// State untuk dialog
|
||||
const showDeleteDialog = ref(false);
|
||||
const isEditMode = ref(false);
|
||||
const editedIndex = ref(-1);
|
||||
const editedItem = ref({
|
||||
id: 0,
|
||||
namaKlinik: '',
|
||||
kode: '',
|
||||
namaRuang: '',
|
||||
});
|
||||
|
||||
// Fungsi untuk tombol aksi
|
||||
const viewItem = (item) => {
|
||||
editedItem.value = { ...item };
|
||||
readOnly.value = true;
|
||||
isEditMode.value = false;
|
||||
showForm.value = true;
|
||||
breadcrumbs.value = [
|
||||
{ title: 'Dashboard', disabled: false, href: '/dashboard' },
|
||||
{ title: 'Setting', disabled: false, href: '/setting' },
|
||||
{ title: 'Master Klinik Ruang', disabled: false, href: '/setting/masterklinikruang' },
|
||||
{ title: 'Detail Ruang Klinik', disabled: true, href: '/setting/viewruang' },
|
||||
];
|
||||
};
|
||||
|
||||
const editItem = (item) => {
|
||||
editedIndex.value = allRuangData.value.findIndex(d => d.id === item.id);
|
||||
editedItem.value = { ...item };
|
||||
isEditMode.value = true;
|
||||
readOnly.value = false;
|
||||
showForm.value = true;
|
||||
breadcrumbs.value = [
|
||||
{ title: 'Dashboard', disabled: false, href: '/dashboard' },
|
||||
{ title: 'Setting', disabled: false, href: '/setting' },
|
||||
{ title: 'Master Klinik Ruang', disabled: false, href: '/setting/masterklinikruang' },
|
||||
{ title: 'Edit Ruang Klinik', disabled: true, href: '/setting/editruang' },
|
||||
];
|
||||
};
|
||||
|
||||
const deleteItem = (item) => {
|
||||
editedIndex.value = allRuangData.value.findIndex(d => d.id === item.id);
|
||||
showDeleteDialog.value = true;
|
||||
};
|
||||
|
||||
const confirmDelete = () => {
|
||||
if (editedIndex.value > -1) {
|
||||
allRuangData.value.splice(editedIndex.value, 1);
|
||||
}
|
||||
closeDeleteDialog();
|
||||
};
|
||||
|
||||
const cancelForm = () => {
|
||||
showForm.value = false;
|
||||
isEditMode.value = false;
|
||||
readOnly.value = false;
|
||||
editedItem.value = {
|
||||
id: 0,
|
||||
namaKlinik: '',
|
||||
kode: '',
|
||||
namaRuang: '',
|
||||
};
|
||||
editedIndex.value = -1;
|
||||
breadcrumbs.value = [
|
||||
{ title: 'Dashboard', disabled: false, href: '/dashboard' },
|
||||
{ title: 'Setting', disabled: false, href: '/setting' },
|
||||
{ title: 'Master Klinik Ruang', disabled: false, href: '/setting/masterklinikruang' },
|
||||
];
|
||||
};
|
||||
|
||||
const closeDeleteDialog = () => {
|
||||
showDeleteDialog.value = false;
|
||||
editedIndex.value = -1;
|
||||
};
|
||||
|
||||
const saveItem = () => {
|
||||
if (isEditMode.value) {
|
||||
Object.assign(allRuangData.value[editedIndex.value], editedItem.value);
|
||||
} else {
|
||||
const newId = allRuangData.value.length > 0 ? Math.max(...allRuangData.value.map(item => item.id)) + 1 : 1;
|
||||
editedItem.value.id = newId;
|
||||
allRuangData.value.push(editedItem.value);
|
||||
}
|
||||
cancelForm();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.master-klinik-ruang-page {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.custom-table {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.v-data-table :deep(th) {
|
||||
font-weight: bold !important;
|
||||
color: #333 !important;
|
||||
}
|
||||
|
||||
.v-data-table :deep(td) {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.v-btn--icon {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.v-select :deep(.v-field__input) {
|
||||
padding-top: 0 !important;
|
||||
padding-bottom: 0 !important;
|
||||
}
|
||||
</style>
|
||||
@@ -110,7 +110,7 @@ const deleteLoket = (item) => {
|
||||
};
|
||||
|
||||
const tambahLoket = () => {
|
||||
router.push({ path: "/Setting/Tambah-Loket" });
|
||||
router.push({ path: "/Setting/TambahLoket" });
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
|
||||
+617
-438
File diff suppressed because it is too large
Load diff
+550
-329
@@ -1,376 +1,597 @@
|
||||
<template>
|
||||
<div class="master-klinik-ruang-page pa-4">
|
||||
<!-- Breadcrumbs -->
|
||||
<v-breadcrumbs :items="breadcrumbs" class="pl-0">
|
||||
<template v-slot:divider>
|
||||
<v-icon icon="mdi-chevron-right"></v-icon>
|
||||
</template>
|
||||
</v-breadcrumbs>
|
||||
|
||||
<!-- Tampilan Formulir Tambah/Edit/View -->
|
||||
<v-card v-if="showForm" class="pa-4 rounded-lg elevation-2">
|
||||
<v-card-title class="text-h5 font-weight-bold mb-4">
|
||||
{{ isEditMode ? 'Edit Ruang Klinik' : readOnly ? 'Detail Ruang Klinik' : 'Tambah Ruang Klinik' }}
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-row>
|
||||
<v-col cols="12" md="6">
|
||||
<v-label class="font-weight-bold">Nama Klinik</v-label>
|
||||
<v-select
|
||||
v-model="editedItem.namaKlinik"
|
||||
:items="['ANAK', 'ANESTESI', 'BEDAH', 'GERIATRI', 'GIGI DAN MULUT', 'GIZI', 'HOM', 'IPD', 'JANTUNG', 'JIWA', 'KANDUNGAN', 'KEMOTERAPI', 'KOMPLEMENTER', 'KUL KEL', 'MATA', 'MCU', 'ONKOLOGI', 'PARU', 'R. TINDAKAN', 'RADIOTERAPI', 'REHAB MEDIK', 'SARAF', 'THT']"
|
||||
placeholder="Pilih Nama Klinik"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
class="mb-2"
|
||||
:readonly="readOnly"
|
||||
></v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-label class="font-weight-bold">Kode Ruang</v-label>
|
||||
<v-text-field
|
||||
v-model="editedItem.kode"
|
||||
placeholder="Masukkan Kode Ruang"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
class="mb-2"
|
||||
:readonly="readOnly"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-label class="font-weight-bold">Nama Ruang</v-label>
|
||||
<v-text-field
|
||||
v-model="editedItem.namaRuang"
|
||||
placeholder="Masukkan Nama Ruang"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
class="mb-2"
|
||||
:readonly="readOnly"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
<v-card-actions class="d-flex justify-start pa-4">
|
||||
<v-btn
|
||||
color="grey-darken-1"
|
||||
variant="flat"
|
||||
class="text-capitalize rounded-lg mr-2"
|
||||
@click="cancelForm"
|
||||
>
|
||||
{{ readOnly ? 'Tutup' : 'Batal' }}
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-if="!readOnly"
|
||||
color="orange-darken-2"
|
||||
variant="flat"
|
||||
class="text-capitalize rounded-lg"
|
||||
@click="saveItem"
|
||||
>
|
||||
Submit
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
|
||||
<!-- Tampilan Tabel Data -->
|
||||
<div v-else>
|
||||
<!-- Banner biru sebagai pengganti header h1 -->
|
||||
<v-card class="d-flex justify-space-between align-center pa-4 mb-4 rounded-lg bg-blue-darken-2 text-white elevation-2">
|
||||
<v-card-title class="d-flex align-center text-h5 font-weight-bold pa-0">
|
||||
<v-icon icon="mdi-hospital-box-outline" class="mr-2" size="40"></v-icon>
|
||||
<span>Master Klinik Ruang</span>
|
||||
</v-card-title>
|
||||
<v-btn
|
||||
color="success"
|
||||
prepend-icon="mdi-plus"
|
||||
rounded
|
||||
class="text-capitalize"
|
||||
@click="showForm = true"
|
||||
>
|
||||
Tambah Baru
|
||||
</v-btn>
|
||||
</v-card>
|
||||
|
||||
<v-card class="pa-4 rounded-lg elevation-2">
|
||||
<v-card-text>
|
||||
<!-- Table controls -->
|
||||
<div class="d-flex flex-wrap align-center justify-space-between mb-4">
|
||||
<div class="d-flex align-center">
|
||||
<span class="mr-2 text-subtitle-1">Show</span>
|
||||
<v-select
|
||||
v-model="itemsPerPage"
|
||||
:items="[10, 25, 50]"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
hide-details
|
||||
style="max-width: 80px;"
|
||||
rounded
|
||||
class="mr-2"
|
||||
></v-select>
|
||||
<span class="text-subtitle-1">entries</span>
|
||||
<v-container>
|
||||
<v-card>
|
||||
<!-- Header -->
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<div class="header-left">
|
||||
<div class="header-icon">
|
||||
<v-icon size="32" color="white">mdi-door-open</v-icon>
|
||||
</div>
|
||||
<div class="d-flex align-center">
|
||||
<v-text-field
|
||||
v-model="search"
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
label="Search"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
hide-details
|
||||
rounded
|
||||
clearable
|
||||
></v-text-field>
|
||||
<div class="header-text">
|
||||
<h1 class="page-title">Master Klinik Ruang</h1>
|
||||
<p class="page-subtitle">Rabu, 13 Agustus 2025 - Manajemen Ruangan</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Data Table dengan paginasi kustom -->
|
||||
<v-data-table
|
||||
:headers="headers"
|
||||
:items="allRuangData"
|
||||
:search="search"
|
||||
:items-per-page="itemsPerPage"
|
||||
v-model:page="page"
|
||||
class="rounded-lg elevation-0 custom-table"
|
||||
<v-btn
|
||||
color="white"
|
||||
@click="openTambahDialog"
|
||||
elevation="0"
|
||||
class="add-btn"
|
||||
>
|
||||
<!-- Slot untuk aksi di setiap baris -->
|
||||
<template v-slot:[`item.actions`]="{ item }">
|
||||
<v-btn icon color="blue" size="small" class="mr-2" @click="viewItem(item)">
|
||||
<v-icon>mdi-eye</v-icon>
|
||||
</v-btn>
|
||||
<v-btn icon color="orange" size="small" class="mr-2" @click="editItem(item)">
|
||||
<v-icon>mdi-pencil</v-icon>
|
||||
</v-btn>
|
||||
<v-btn icon color="red" size="small" @click="deleteItem(item)">
|
||||
<v-icon>mdi-delete</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
|
||||
<template v-slot:no-data>
|
||||
<v-alert :value="true" color="grey-lighten-3" icon="mdi-information">
|
||||
Tidak ada data yang tersedia.
|
||||
</v-alert>
|
||||
</template>
|
||||
<v-icon left size="20">mdi-plus-circle</v-icon>
|
||||
Tambah Ruang
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Slot kustom untuk footer tabel (paginasi) -->
|
||||
<template v-slot:bottom>
|
||||
<v-row class="ma-2">
|
||||
<v-col cols="12" sm="6" class="d-flex align-center justify-start text-caption text-grey">
|
||||
{{ showingEntriesText }}
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" class="d-flex align-center justify-end">
|
||||
<v-pagination
|
||||
v-model="page"
|
||||
:length="pageCount"
|
||||
rounded="circle"
|
||||
:total-visible="5"
|
||||
></v-pagination>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Delete Dialog -->
|
||||
<v-dialog v-model="showDeleteDialog" max-width="400px">
|
||||
<v-card class="pa-4 rounded-lg">
|
||||
<v-card-title class="text-h6 font-weight-bold">Hapus Data</v-card-title>
|
||||
<v-card-text>Apakah Anda yakin ingin menghapus data ini?</v-card-text>
|
||||
<v-card-actions class="d-flex justify-end">
|
||||
<v-btn color="grey-darken-1" variant="text" @click="closeDeleteDialog">Batal</v-btn>
|
||||
<v-btn color="red" variant="text" @click="confirmDelete">Hapus</v-btn>
|
||||
</v-card-actions>
|
||||
<!-- Table -->
|
||||
<v-card-text>
|
||||
<v-data-table
|
||||
:headers="headers"
|
||||
:items="ruangData"
|
||||
:items-per-page="10"
|
||||
class="elevation-0"
|
||||
>
|
||||
<template v-slot:item.namaKlinik="{ item }">
|
||||
<v-chip size="small" color="primary" variant="outlined">
|
||||
{{ item.kodeKlinik }}
|
||||
</v-chip>
|
||||
<span class="ml-2">{{ item.namaKlinik }}</span>
|
||||
</template>
|
||||
|
||||
<template v-slot:item.aksi="{ item }">
|
||||
<v-btn
|
||||
size="small"
|
||||
color="#1976d2"
|
||||
@click="openEditDialog(item)"
|
||||
class="mr-2"
|
||||
variant="flat"
|
||||
>
|
||||
<v-icon size="16" left>mdi-pencil</v-icon>
|
||||
Edit
|
||||
</v-btn>
|
||||
<v-btn
|
||||
size="small"
|
||||
color="#f44336"
|
||||
@click="deleteRuang(item)"
|
||||
variant="flat"
|
||||
>
|
||||
<v-icon size="16" left>mdi-delete</v-icon>
|
||||
Delete
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- Dialog Tambah/Edit -->
|
||||
<v-dialog v-model="dialog" max-width="800px" persistent>
|
||||
<v-card>
|
||||
<v-card-title class="dialog-header">
|
||||
<span class="dialog-title">{{ isEdit ? 'Edit Klinik Ruang' : 'Tambah Klinik Ruang' }}</span>
|
||||
<v-btn icon variant="text" @click="closeDialog" size="small">
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
</v-card-title>
|
||||
|
||||
<v-divider></v-divider>
|
||||
|
||||
<v-card-text class="dialog-content">
|
||||
<v-form ref="formRef">
|
||||
<!-- Klinik Selection -->
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<div class="section-header">
|
||||
<v-icon color="primary" size="20">mdi-hospital-building</v-icon>
|
||||
<span class="section-title">Pilih Klinik</span>
|
||||
</div>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<v-autocomplete
|
||||
label="Kode Klinik"
|
||||
v-model="formData.kodeKlinik"
|
||||
:items="klinikList"
|
||||
item-title="kode"
|
||||
item-value="kode"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
:rules="[v => !!v || 'Kode klinik harus dipilih']"
|
||||
required
|
||||
hide-details="auto"
|
||||
placeholder="Masukkan Kode Klinik"
|
||||
@update:model-value="onKlinikChange"
|
||||
>
|
||||
<template v-slot:item="{ props, item }">
|
||||
<v-list-item v-bind="props">
|
||||
<template v-slot:prepend>
|
||||
<v-chip size="small" color="primary">{{ item.raw.kode }}</v-chip>
|
||||
</template>
|
||||
<v-list-item-title>{{ item.raw.nama }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
</template>
|
||||
</v-autocomplete>
|
||||
<small class="text-grey">Masukan Kode Ruang 2 Huruf</small>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
label="Nama Klinik"
|
||||
v-model="formData.namaKlinik"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
readonly
|
||||
hide-details="auto"
|
||||
placeholder="Nama akan terisi otomatis"
|
||||
bg-color="grey-lighten-4"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- Ruang Section -->
|
||||
<v-row class="mt-4">
|
||||
<v-col cols="12">
|
||||
<div class="section-header">
|
||||
<v-icon color="primary" size="20">mdi-door</v-icon>
|
||||
<span class="section-title">Daftar Ruangan</span>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn
|
||||
size="small"
|
||||
color="success"
|
||||
variant="flat"
|
||||
@click="addRuang"
|
||||
>
|
||||
<v-icon size="18" left>mdi-plus</v-icon>
|
||||
Add Ruang
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12">
|
||||
<div class="ruang-list">
|
||||
<div
|
||||
v-for="(ruang, index) in formData.ruangList"
|
||||
:key="index"
|
||||
class="ruang-item"
|
||||
>
|
||||
<v-row align="center">
|
||||
<v-col cols="12" md="1">
|
||||
<div class="ruang-number">
|
||||
<v-icon size="16">mdi-numeric-{{ index + 1 }}-circle</v-icon>
|
||||
</div>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="3">
|
||||
<v-text-field
|
||||
label="Nomor Ruang"
|
||||
v-model="ruang.nomorRuang"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
hide-details="auto"
|
||||
placeholder="Masukan Nomor Ruangan"
|
||||
prepend-inner-icon="mdi-pound"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="4">
|
||||
<v-text-field
|
||||
label="Nama Ruang"
|
||||
v-model="ruang.namaRuang"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
hide-details="auto"
|
||||
placeholder="Masukan Nama Klinik Ruang"
|
||||
prepend-inner-icon="mdi-door-open"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="3">
|
||||
<v-text-field
|
||||
label="Nomor Screen"
|
||||
v-model="ruang.nomorScreen"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
hide-details="auto"
|
||||
placeholder="Screen Klinik Ruang"
|
||||
prepend-inner-icon="mdi-monitor"
|
||||
></v-text-field>
|
||||
<small class="text-grey">Masukan Nomor Screen Ruang</small>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="1" class="d-flex justify-center">
|
||||
<v-btn
|
||||
v-if="formData.ruangList.length > 1"
|
||||
icon
|
||||
size="small"
|
||||
color="error"
|
||||
variant="text"
|
||||
@click="removeRuang(index)"
|
||||
>
|
||||
<v-icon>mdi-delete</v-icon>
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
|
||||
<v-alert
|
||||
v-if="formData.ruangList.length === 0"
|
||||
type="info"
|
||||
variant="tonal"
|
||||
class="mt-2"
|
||||
>
|
||||
<v-icon left>mdi-information</v-icon>
|
||||
Klik "Add Ruang" untuk menambahkan ruangan
|
||||
</v-alert>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
|
||||
<v-divider></v-divider>
|
||||
|
||||
<v-card-actions class="dialog-actions">
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn
|
||||
color="grey-lighten-2"
|
||||
@click="closeDialog"
|
||||
class="action-btn"
|
||||
>
|
||||
<v-icon left size="18">mdi-close</v-icon>
|
||||
Cancel
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="warning"
|
||||
@click="submitForm"
|
||||
class="action-btn"
|
||||
>
|
||||
<v-icon left size="18">mdi-content-save</v-icon>
|
||||
Submit
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { ref } from 'vue';
|
||||
|
||||
definePageMeta({
|
||||
middleware:['auth']
|
||||
})
|
||||
|
||||
// State untuk menampilkan/menyembunyikan formulir
|
||||
const showForm = ref(false);
|
||||
const readOnly = ref(false);
|
||||
|
||||
// Data dummy untuk Master Klinik Ruang
|
||||
const allRuangData = ref([
|
||||
{ id: 1, namaKlinik: 'ANAK', kode: 'AN-01', namaRuang: 'RUANG ANAK 1' },
|
||||
{ id: 2, namaKlinik: 'ANAK', kode: 'AN-02', namaRuang: 'RUANG ANAK 2' },
|
||||
{ id: 3, namaKlinik: 'ANESTESI', kode: 'AS-01', namaRuang: 'RUANG ANESTESI 1' },
|
||||
{ id: 4, namaKlinik: 'BEDAH', kode: 'BD-01', namaRuang: 'RUANG BEDAH 1' },
|
||||
{ id: 5, namaKlinik: 'GIGI DAN MULUT', kode: 'GI-01', namaRuang: 'RUANG GIGI DAN MULUT 1' },
|
||||
{ id: 6, namaKlinik: 'GERIATRI', kode: 'GR-01', namaRuang: 'RUANG GERIATRI 1' },
|
||||
{ id: 7, namaKlinik: 'GIZI', kode: 'GZ-01', namaRuang: 'RUANG GIZI 1' },
|
||||
{ id: 8, namaKlinik: 'HOM', kode: 'HO-01', namaRuang: 'RUANG HOM 1' },
|
||||
{ id: 9, namaKlinik: 'IPD', kode: 'IP-01', namaRuang: 'RUANG IPD 1' },
|
||||
{ id: 10, namaKlinik: 'JANTUNG', kode: 'JT-01', namaRuang: 'RUANG JANTUNG 1' },
|
||||
{ id: 11, namaKlinik: 'JIWA', kode: 'JW-01', namaRuang: 'RUANG JIWA 1' },
|
||||
{ id: 12, namaKlinik: 'KANDUNGAN', kode: 'OB-01', namaRuang: 'RUANG KANDUNGAN 1' },
|
||||
{ id: 13, namaKlinik: 'KANDUNGAN', kode: 'OB-02', namaRuang: 'RUANG KANDUNGAN 2' },
|
||||
{ id: 14, namaKlinik: 'KEMOTERAPI', kode: 'KT-01', namaRuang: 'RUANG KEMOTERAPI 1' },
|
||||
{ id: 15, namaKlinik: 'KOMPLEMENTER', kode: 'KN-01', namaRuang: 'RUANG KOMPLEMENTER 1' },
|
||||
{ id: 16, namaKlinik: 'KUL KEL', kode: 'KL-01', namaRuang: 'RUANG KUL KEL 1' },
|
||||
{ id: 17, namaKlinik: 'MATA', kode: 'MT-01', namaRuang: 'RUANG MATA 1' },
|
||||
{ id: 18, namaKlinik: 'MCU', kode: 'MC-01', namaRuang: 'RUANG MCU 1' },
|
||||
{ id: 19, namaKlinik: 'ONKOLOGI', kode: 'ON-01', namaRuang: 'RUANG ONKOLOGI 1' },
|
||||
{ id: 20, namaKlinik: 'PARU', kode: 'PR-01', namaRuang: 'RUANG PARU 1' },
|
||||
{ id: 21, namaKlinik: 'R. TINDAKAN', kode: 'RT-01', namaRuang: 'RUANG R. TINDAKAN 1' },
|
||||
{ id: 22, namaKlinik: 'RADIOTERAPI', kode: 'RD-01', namaRuang: 'RUANG RADIOTERAPI 1' },
|
||||
{ id: 23, namaKlinik: 'REHAB MEDIK', kode: 'RM-01', namaRuang: 'RUANG REHAB MEDIK 1' },
|
||||
{ id: 24, namaKlinik: 'SARAF', kode: 'NU-01', namaRuang: 'RUANG SARAF 1' },
|
||||
{ id: 25, namaKlinik: 'THT', kode: 'TH-01', namaRuang: 'RUANG THT 1' },
|
||||
]);
|
||||
const dialog = ref(false);
|
||||
const isEdit = ref(false);
|
||||
const formRef = ref(null);
|
||||
|
||||
const headers = ref([
|
||||
{ title: 'No', key: 'id' },
|
||||
{ title: 'Nama Klinik', key: 'namaKlinik', sortable: true },
|
||||
{ title: 'Kode Ruang', key: 'kode', sortable: true },
|
||||
{ title: 'Nama Ruang', key: 'namaRuang', sortable: true },
|
||||
{ title: 'Aksi', key: 'actions', sortable: false },
|
||||
{ title: 'No', value: 'no', sortable: true },
|
||||
{ title: 'Nama Klinik', value: 'namaKlinik', sortable: true },
|
||||
{ title: 'Kode', value: 'kodeKlinik', sortable: true },
|
||||
{ title: 'Nama Ruang', value: 'namaRuang', sortable: true },
|
||||
{ title: 'Aksi', value: 'aksi', sortable: false },
|
||||
]);
|
||||
|
||||
// Breadcrumbs
|
||||
const breadcrumbs = ref([
|
||||
{ title: 'Dashboard', disabled: false, href: '/dashboard' },
|
||||
{ title: 'Setting', disabled: false, href: '/setting' },
|
||||
{ title: 'Master Klinik Ruang', disabled: false, href: '/setting/masterklinikruang' },
|
||||
// List klinik dari Master Klinik
|
||||
const klinikList = ref([
|
||||
{ kode: 'AN', nama: 'ANAK' },
|
||||
{ kode: 'AS', nama: 'ANESTESI' },
|
||||
{ kode: 'BD', nama: 'BEDAH' },
|
||||
{ kode: 'GR', nama: 'GERIATRI' },
|
||||
{ kode: 'GI', nama: 'GIGI DAN MULUT' },
|
||||
{ kode: 'GZ', nama: 'GIZI' },
|
||||
{ kode: 'HO', nama: 'HOM' },
|
||||
{ kode: 'IP', nama: 'IPD' },
|
||||
{ kode: 'JT', nama: 'JANTUNG' },
|
||||
{ kode: 'JW', nama: 'JIWA' },
|
||||
{ kode: 'KK', nama: 'KULIT KELAMIN' },
|
||||
{ kode: 'MT', nama: 'MATA' },
|
||||
{ kode: 'OB', nama: 'OBGYN' },
|
||||
{ kode: 'PR', nama: 'PARU' },
|
||||
{ kode: 'RT', nama: 'RADIOTERAPI' },
|
||||
]);
|
||||
|
||||
// State untuk tabel dan paginasi
|
||||
const itemsPerPage = ref(10);
|
||||
const search = ref('');
|
||||
const page = ref(1);
|
||||
const ruangData = ref([
|
||||
{
|
||||
id: 1,
|
||||
no: 1,
|
||||
kodeKlinik: 'AN',
|
||||
namaKlinik: 'ANAK',
|
||||
namaRuang: 'R. TINDAKAN',
|
||||
ruangList: [
|
||||
{ nomorRuang: '1', namaRuang: 'R. TINDAKAN', nomorScreen: '101' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
no: 2,
|
||||
kodeKlinik: 'AS',
|
||||
namaKlinik: 'ANESTESI',
|
||||
namaRuang: 'Ruang 1, Ruang 2',
|
||||
ruangList: [
|
||||
{ nomorRuang: '1', namaRuang: 'Ruang 1', nomorScreen: '201' },
|
||||
{ nomorRuang: '2', namaRuang: 'Ruang 2', nomorScreen: '202' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
no: 3,
|
||||
kodeKlinik: 'BD',
|
||||
namaKlinik: 'BEDAH',
|
||||
namaRuang: 'Ruang Konsultasi',
|
||||
ruangList: [
|
||||
{ nomorRuang: '1', namaRuang: 'Ruang Konsultasi', nomorScreen: '301' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
no: 4,
|
||||
kodeKlinik: 'GR',
|
||||
namaKlinik: 'GERIATRI',
|
||||
namaRuang: 'Ruang Pemeriksaan',
|
||||
ruangList: [
|
||||
{ nomorRuang: '1', namaRuang: 'Ruang Pemeriksaan', nomorScreen: '401' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
no: 5,
|
||||
kodeKlinik: 'GI',
|
||||
namaKlinik: 'GIGI DAN MULUT',
|
||||
namaRuang: 'Ruang 1, Ruang 2, Ruang 3',
|
||||
ruangList: [
|
||||
{ nomorRuang: '1', namaRuang: 'Ruang 1', nomorScreen: '501' },
|
||||
{ nomorRuang: '2', namaRuang: 'Ruang 2', nomorScreen: '502' },
|
||||
{ nomorRuang: '3', namaRuang: 'Ruang 3', nomorScreen: '503' }
|
||||
]
|
||||
},
|
||||
]);
|
||||
|
||||
// Computed properties untuk paginasi kustom
|
||||
const pageCount = computed(() => {
|
||||
return Math.ceil(allRuangData.value.length / itemsPerPage.value);
|
||||
});
|
||||
|
||||
const showingEntriesText = computed(() => {
|
||||
const start = (page.value - 1) * itemsPerPage.value + 1;
|
||||
const end = Math.min(page.value * itemsPerPage.value, allRuangData.value.length);
|
||||
const total = allRuangData.value.length;
|
||||
return `Showing ${start} to ${end} of ${total} entries`;
|
||||
});
|
||||
|
||||
// State untuk dialog
|
||||
const showDeleteDialog = ref(false);
|
||||
const isEditMode = ref(false);
|
||||
const editedIndex = ref(-1);
|
||||
const editedItem = ref({
|
||||
id: 0,
|
||||
const formData = ref({
|
||||
id: null,
|
||||
kodeKlinik: '',
|
||||
namaKlinik: '',
|
||||
kode: '',
|
||||
namaRuang: '',
|
||||
ruangList: [
|
||||
{ nomorRuang: '', namaRuang: '', nomorScreen: '' }
|
||||
],
|
||||
});
|
||||
|
||||
// Fungsi untuk tombol aksi
|
||||
const viewItem = (item) => {
|
||||
editedItem.value = { ...item };
|
||||
readOnly.value = true;
|
||||
isEditMode.value = false;
|
||||
showForm.value = true;
|
||||
breadcrumbs.value = [
|
||||
{ title: 'Dashboard', disabled: false, href: '/dashboard' },
|
||||
{ title: 'Setting', disabled: false, href: '/setting' },
|
||||
{ title: 'Master Klinik Ruang', disabled: false, href: '/setting/masterklinikruang' },
|
||||
{ title: 'Detail Ruang Klinik', disabled: true, href: '/setting/viewruang' },
|
||||
];
|
||||
};
|
||||
|
||||
const editItem = (item) => {
|
||||
editedIndex.value = allRuangData.value.findIndex(d => d.id === item.id);
|
||||
editedItem.value = { ...item };
|
||||
isEditMode.value = true;
|
||||
readOnly.value = false;
|
||||
showForm.value = true;
|
||||
breadcrumbs.value = [
|
||||
{ title: 'Dashboard', disabled: false, href: '/dashboard' },
|
||||
{ title: 'Setting', disabled: false, href: '/setting' },
|
||||
{ title: 'Master Klinik Ruang', disabled: false, href: '/setting/masterklinikruang' },
|
||||
{ title: 'Edit Ruang Klinik', disabled: true, href: '/setting/editruang' },
|
||||
];
|
||||
};
|
||||
|
||||
const deleteItem = (item) => {
|
||||
editedIndex.value = allRuangData.value.findIndex(d => d.id === item.id);
|
||||
showDeleteDialog.value = true;
|
||||
};
|
||||
|
||||
const confirmDelete = () => {
|
||||
if (editedIndex.value > -1) {
|
||||
allRuangData.value.splice(editedIndex.value, 1);
|
||||
const onKlinikChange = (kode) => {
|
||||
const selectedKlinik = klinikList.value.find(k => k.kode === kode);
|
||||
if (selectedKlinik) {
|
||||
formData.value.namaKlinik = selectedKlinik.nama;
|
||||
}
|
||||
closeDeleteDialog();
|
||||
};
|
||||
|
||||
const cancelForm = () => {
|
||||
showForm.value = false;
|
||||
isEditMode.value = false;
|
||||
readOnly.value = false;
|
||||
editedItem.value = {
|
||||
id: 0,
|
||||
namaKlinik: '',
|
||||
kode: '',
|
||||
const addRuang = () => {
|
||||
formData.value.ruangList.push({
|
||||
nomorRuang: '',
|
||||
namaRuang: '',
|
||||
};
|
||||
editedIndex.value = -1;
|
||||
breadcrumbs.value = [
|
||||
{ title: 'Dashboard', disabled: false, href: '/dashboard' },
|
||||
{ title: 'Setting', disabled: false, href: '/setting' },
|
||||
{ title: 'Master Klinik Ruang', disabled: false, href: '/setting/masterklinikruang' },
|
||||
];
|
||||
nomorScreen: ''
|
||||
});
|
||||
};
|
||||
|
||||
const closeDeleteDialog = () => {
|
||||
showDeleteDialog.value = false;
|
||||
editedIndex.value = -1;
|
||||
const removeRuang = (index) => {
|
||||
if (formData.value.ruangList.length > 1) {
|
||||
formData.value.ruangList.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
const saveItem = () => {
|
||||
if (isEditMode.value) {
|
||||
Object.assign(allRuangData.value[editedIndex.value], editedItem.value);
|
||||
const openTambahDialog = () => {
|
||||
isEdit.value = false;
|
||||
resetForm();
|
||||
dialog.value = true;
|
||||
};
|
||||
|
||||
const openEditDialog = (item) => {
|
||||
isEdit.value = true;
|
||||
formData.value = {
|
||||
id: item.id,
|
||||
kodeKlinik: item.kodeKlinik,
|
||||
namaKlinik: item.namaKlinik,
|
||||
ruangList: JSON.parse(JSON.stringify(item.ruangList)),
|
||||
};
|
||||
dialog.value = true;
|
||||
};
|
||||
|
||||
const closeDialog = () => {
|
||||
dialog.value = false;
|
||||
resetForm();
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: null,
|
||||
kodeKlinik: '',
|
||||
namaKlinik: '',
|
||||
ruangList: [
|
||||
{ nomorRuang: '', namaRuang: '', nomorScreen: '' }
|
||||
],
|
||||
};
|
||||
if (formRef.value) {
|
||||
formRef.value.reset();
|
||||
}
|
||||
};
|
||||
|
||||
const submitForm = async () => {
|
||||
const { valid } = await formRef.value.validate();
|
||||
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (formData.value.ruangList.length === 0) {
|
||||
alert('Tambahkan minimal 1 ruangan');
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate nama ruang untuk display di table
|
||||
const namaRuangDisplay = formData.value.ruangList
|
||||
.map(r => r.namaRuang)
|
||||
.filter(n => n)
|
||||
.join(', ');
|
||||
|
||||
if (isEdit.value) {
|
||||
const index = ruangData.value.findIndex(r => r.id === formData.value.id);
|
||||
if (index !== -1) {
|
||||
ruangData.value[index] = {
|
||||
...ruangData.value[index],
|
||||
...formData.value,
|
||||
namaRuang: namaRuangDisplay,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
const newId = allRuangData.value.length > 0 ? Math.max(...allRuangData.value.map(item => item.id)) + 1 : 1;
|
||||
editedItem.value.id = newId;
|
||||
allRuangData.value.push(editedItem.value);
|
||||
const newId = Math.max(...ruangData.value.map(r => r.id)) + 1;
|
||||
const newNo = ruangData.value.length + 1;
|
||||
ruangData.value.push({
|
||||
id: newId,
|
||||
no: newNo,
|
||||
...formData.value,
|
||||
namaRuang: namaRuangDisplay,
|
||||
});
|
||||
}
|
||||
|
||||
closeDialog();
|
||||
};
|
||||
|
||||
const deleteRuang = (item) => {
|
||||
if (confirm(`Hapus ruangan ${item.namaRuang} dari klinik ${item.namaKlinik}?`)) {
|
||||
const index = ruangData.value.findIndex(r => r.id === item.id);
|
||||
if (index !== -1) {
|
||||
ruangData.value.splice(index, 1);
|
||||
ruangData.value.forEach((r, idx) => {
|
||||
r.no = idx + 1;
|
||||
});
|
||||
}
|
||||
}
|
||||
cancelForm();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.master-klinik-ruang-page {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
background-color: #f5f7fa;
|
||||
min-height: 100vh;
|
||||
.page-header {
|
||||
background: linear-gradient(135deg, #4caf50 0%, #388e3c 100%);
|
||||
border-radius: 16px 16px 0 0;
|
||||
box-shadow: 0 4px 16px rgba(76, 175, 80, 0.2);
|
||||
}
|
||||
|
||||
.custom-table {
|
||||
border: none;
|
||||
.header-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 32px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.v-data-table :deep(th) {
|
||||
font-weight: bold !important;
|
||||
color: #333 !important;
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.v-data-table :deep(td) {
|
||||
vertical-align: middle;
|
||||
.header-icon {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 16px;
|
||||
padding: 16px;
|
||||
margin-right: 20px;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.v-btn--icon {
|
||||
.page-title {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
margin: 4px 0 0 0;
|
||||
opacity: 0.9;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
font-weight: 600;
|
||||
text-transform: none;
|
||||
letter-spacing: 0.5px;
|
||||
padding: 0 24px !important;
|
||||
height: 44px !important;
|
||||
color: #4caf50 !important;
|
||||
}
|
||||
|
||||
.dialog-header {
|
||||
background: linear-gradient(135deg, #4caf50 0%, #388e3c 100%);
|
||||
color: white;
|
||||
padding: 20px 24px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.dialog-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.dialog-content {
|
||||
padding: 24px !important;
|
||||
max-height: 650px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.dialog-actions {
|
||||
padding: 16px 24px;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
text-transform: none;
|
||||
font-weight: 600;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 12px 16px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid #4caf50;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.v-select :deep(.v-field__input) {
|
||||
padding-top: 0 !important;
|
||||
padding-bottom: 0 !important;
|
||||
.section-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #424242;
|
||||
}
|
||||
</style>
|
||||
|
||||
.ruang-list {
|
||||
background: #f8f9fa;
|
||||
border: 2px solid #e8f5e9;
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.ruang-item {
|
||||
background: white;
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 12px;
|
||||
border: 1px solid #e0e0e0;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
.ruang-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.ruang-number {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(135deg, #4caf50 0%, #388e3c 100%);
|
||||
color: white;
|
||||
padding: 8px;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,176 @@
|
||||
<template>
|
||||
<v-container>
|
||||
<v-card>
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<div class="header-left">
|
||||
<div class="header-icon">
|
||||
<v-icon size="32" color="white">mdi-view-dashboard</v-icon>
|
||||
</div>
|
||||
<div class="header-text">
|
||||
<h1 class="page-title">Master Loket</h1>
|
||||
<p class="page-subtitle">Rabu, 13 Agustus 2025 - Pelayanan</p>
|
||||
</div>
|
||||
</div>
|
||||
<v-btn
|
||||
color="white"
|
||||
@click="tambahLoket"
|
||||
elevation="0"
|
||||
class="add-btn"
|
||||
>
|
||||
<v-icon left size="20">mdi-plus-circle</v-icon>
|
||||
Tambah Loket
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
<TabelData
|
||||
:headers="loketHeaders"
|
||||
:items="loketData"
|
||||
title="Master Loket"
|
||||
>
|
||||
<template #actions="{ item }">
|
||||
<v-btn
|
||||
small
|
||||
color="#ff9248"
|
||||
@click="editLoket(item)"
|
||||
class="mr-2"
|
||||
style="color: white"
|
||||
>
|
||||
Edit
|
||||
</v-btn>
|
||||
<v-btn
|
||||
small
|
||||
color="grey-lighten-4"
|
||||
@click="deleteLoket(item)"
|
||||
>
|
||||
Delete
|
||||
</v-btn>
|
||||
</template>
|
||||
</TabelData>
|
||||
</v-card>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import TabelData from "../../components/TabelData.vue";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const loketHeaders = ref([
|
||||
{ title: "No", value: "no" },
|
||||
{ title: "Nama Loket", value: "namaLoket" },
|
||||
{ title: "Kuota", value: "kuota" },
|
||||
{ title: "Pelayanan", value: "pelayanan" },
|
||||
{ title: "Pembayaran", value: "pembayaran" },
|
||||
{ title: "Keterangan", value: "keterangan" },
|
||||
{ title: "Aksi", value: "aksi", sortable: false },
|
||||
]);
|
||||
|
||||
const loketData = ref([
|
||||
{
|
||||
id: 1,
|
||||
no: 1,
|
||||
namaLoket: "Loket 1",
|
||||
kuota: 500,
|
||||
pelayanan: ["RADIOTERAPI", "REHAB MEDIK", "TINDAKAN"],
|
||||
pembayaran: "JKN",
|
||||
keterangan: "ONLINE",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
no: 2,
|
||||
namaLoket: "Loket 2",
|
||||
kuota: 666,
|
||||
pelayanan: ["JIWA", "SARAF"],
|
||||
pembayaran: "JKN",
|
||||
keterangan: "ONLINE",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
no: 3,
|
||||
namaLoket: "Loket 3",
|
||||
kuota: 666,
|
||||
pelayanan: ["ANESTESI", "JANTUNG"],
|
||||
pembayaran: "JKN",
|
||||
keterangan: "ONLINE",
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
no: 4,
|
||||
namaLoket: "Loket 4",
|
||||
kuota: 3676,
|
||||
pelayanan: ["KULIT KELAMIN", "PARU"],
|
||||
pembayaran: "JKN",
|
||||
keterangan: "ONLINE",
|
||||
},
|
||||
]);
|
||||
|
||||
const editLoket = (item) => {
|
||||
router.push({ path: `/Setting/Edit-Loket/${item.id}` });
|
||||
};
|
||||
|
||||
const deleteLoket = (item) => {
|
||||
const index = loketData.value.findIndex((loket) => loket.id === item.id);
|
||||
if (index !== -1) {
|
||||
loketData.value.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
const tambahLoket = () => {
|
||||
router.push({ path: "/Setting/TambahLoket" });
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page-header {
|
||||
background: linear-gradient(135deg, #1976d2 0%, #1565c0 100%);
|
||||
border-radius: 16px;
|
||||
margin-bottom: 24px;
|
||||
box-shadow: 0 8px 32px rgba(25, 118, 210, 0.3);
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 32px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 16px;
|
||||
padding: 16px;
|
||||
margin-right: 20px;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
margin: 4px 0 0 0;
|
||||
opacity: 0.9;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
font-weight: 600;
|
||||
text-transform: none;
|
||||
letter-spacing: 0.5px;
|
||||
padding: 0 24px !important;
|
||||
height: 44px !important;
|
||||
color: #1976d2 !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,310 @@
|
||||
<template>
|
||||
<v-container>
|
||||
<v-card class="form-card" elevation="0">
|
||||
<!-- Header -->
|
||||
<div class="form-header">
|
||||
<div class="header-content">
|
||||
<div class="header-left">
|
||||
<v-btn
|
||||
icon
|
||||
variant="text"
|
||||
color="white"
|
||||
@click="cancelAdd"
|
||||
class="back-btn"
|
||||
>
|
||||
<v-icon>mdi-arrow-left</v-icon>
|
||||
</v-btn>
|
||||
<div class="header-text">
|
||||
<h1 class="page-title">Tambah Loket Baru</h1>
|
||||
<p class="page-subtitle">Isi form untuk menambahkan loket</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Form Content -->
|
||||
<v-card-text class="form-content">
|
||||
<v-form @submit.prevent="simpanLoket" ref="formRef">
|
||||
<!-- Section: Informasi Loket -->
|
||||
<div class="form-section">
|
||||
<h3 class="section-title">Informasi Loket</h3>
|
||||
<v-row>
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
label="Nama Loket"
|
||||
v-model="loket.namaLoket"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
:rules="[v => !!v || 'Nama loket harus diisi']"
|
||||
required
|
||||
hide-details="auto"
|
||||
class="form-field"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
label="Kuota Bangku"
|
||||
v-model="loket.kuota"
|
||||
type="number"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
:rules="[v => !!v || 'Kuota harus diisi', v => v > 0 || 'Kuota harus lebih dari 0']"
|
||||
required
|
||||
hide-details="auto"
|
||||
class="form-field"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
|
||||
<!-- Section: Konfigurasi -->
|
||||
<div class="form-section">
|
||||
<h3 class="section-title">Konfigurasi</h3>
|
||||
<v-row>
|
||||
<v-col cols="12" md="4">
|
||||
<v-select
|
||||
label="Status Pelayanan"
|
||||
:items="['RAWAT JALAN', 'RAWAT INAP']"
|
||||
v-model="loket.statusPelayanan"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
:rules="[v => !!v || 'Status pelayanan harus dipilih']"
|
||||
required
|
||||
hide-details="auto"
|
||||
class="form-field"
|
||||
></v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" md="4">
|
||||
<v-select
|
||||
label="Pembayaran"
|
||||
:items="['JKN', 'UMUM', 'SPM', 'JKMM', 'JAMPERSAL', 'T4', 'KARYAWAN']"
|
||||
v-model="loket.pembayaran"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
:rules="[v => !!v || 'Pembayaran harus dipilih']"
|
||||
required
|
||||
hide-details="auto"
|
||||
class="form-field"
|
||||
></v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" md="4">
|
||||
<v-select
|
||||
label="Keterangan"
|
||||
:items="['ONLINE', 'OFFLINE']"
|
||||
v-model="loket.keterangan"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
:rules="[v => !!v || 'Keterangan harus dipilih']"
|
||||
required
|
||||
hide-details="auto"
|
||||
class="form-field"
|
||||
></v-select>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
|
||||
<!-- Section: Pelayanan -->
|
||||
<div class="form-section">
|
||||
<h3 class="section-title">Pilih Pelayanan</h3>
|
||||
<div class="table-wrapper">
|
||||
<TabelLayanan
|
||||
:headers="serviceHeaders"
|
||||
:items="availableServices"
|
||||
v-model:selected-items="loket.pelayanan"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="form-actions">
|
||||
<v-btn
|
||||
color="#1976d2"
|
||||
type="submit"
|
||||
size="large"
|
||||
class="action-btn save-btn"
|
||||
>
|
||||
<v-icon left>mdi-content-save</v-icon>
|
||||
Simpan Loket
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="grey-lighten-2"
|
||||
@click="cancelAdd"
|
||||
size="large"
|
||||
class="action-btn cancel-btn"
|
||||
>
|
||||
<v-icon left>mdi-close</v-icon>
|
||||
Batal
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import TabelLayanan from '../../components/TabelLayanan.vue';
|
||||
|
||||
const router = useRouter();
|
||||
const formRef = ref(null);
|
||||
|
||||
const loket = ref({
|
||||
namaLoket: '',
|
||||
kuota: null,
|
||||
statusPelayanan: '',
|
||||
pembayaran: '',
|
||||
keterangan: '',
|
||||
pelayanan: [],
|
||||
});
|
||||
|
||||
const serviceHeaders = ref([
|
||||
{ title: '#', value: 'no', sortable: false },
|
||||
{ title: 'Kode', value: 'id' },
|
||||
{ title: 'Klinik', value: 'nama' },
|
||||
{ title: 'Pilih', value: 'pilih', sortable: false },
|
||||
]);
|
||||
|
||||
const availableServices = ref([
|
||||
{ no: 1, id: 'AN', nama: 'ANAK' },
|
||||
{ no: 2, id: 'AS', nama: 'ANESTESI' },
|
||||
{ no: 3, id: 'BD', nama: 'BEDAH' },
|
||||
{ no: 4, id: 'GR', nama: 'GERIATRI' },
|
||||
{ no: 5, id: 'GI', nama: 'GIGI DAN MULUT' },
|
||||
{ no: 6, id: 'GZ', nama: 'GIZI' },
|
||||
{ no: 7, id: 'HO', nama: 'HOM' },
|
||||
{ no: 8, id: 'IP', nama: 'IPD' },
|
||||
{ no: 9, id: 'JT', nama: 'JANTUNG' },
|
||||
{ no: 10, id: 'JW', nama: 'JIWA' },
|
||||
{ no: 11, id: 'KK', nama: 'KULIT KELAMIN' },
|
||||
{ no: 12, id: 'PR', nama: 'PARU' },
|
||||
{ no: 13, id: 'RT', nama: 'RADIOTERAPI' },
|
||||
{ no: 14, id: 'RM', nama: 'REHAB MEDIK' },
|
||||
{ no: 15, id: 'SR', nama: 'SARAF' },
|
||||
{ no: 16, id: 'TD', nama: 'TINDAKAN' },
|
||||
]);
|
||||
|
||||
const simpanLoket = async () => {
|
||||
const { valid } = await formRef.value.validate();
|
||||
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Validasi pelayanan
|
||||
if (loket.value.pelayanan.length === 0) {
|
||||
alert('Pilih minimal 1 pelayanan');
|
||||
return;
|
||||
}
|
||||
|
||||
// Dalam aplikasi nyata, ini adalah tempat untuk memanggil API create data
|
||||
console.log('Data loket baru:', loket.value);
|
||||
|
||||
// Kembali ke halaman master
|
||||
router.push('/Setting/Master-Loket');
|
||||
};
|
||||
|
||||
const cancelAdd = () => {
|
||||
router.back();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.form-card {
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
border: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.form-header {
|
||||
background: linear-gradient(135deg, #1976d2 0%, #1565c0 100%);
|
||||
box-shadow: 0 4px 16px rgba(25, 118, 210, 0.2);
|
||||
}
|
||||
|
||||
.header-content {
|
||||
padding: 28px 32px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 26px;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
margin: 4px 0 0 0;
|
||||
opacity: 0.9;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.form-content {
|
||||
padding: 32px !important;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.form-section {
|
||||
background: white;
|
||||
padding: 24px;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 24px;
|
||||
border: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #1976d2;
|
||||
margin: 0 0 20px 0;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 2px solid #e3f2fd;
|
||||
}
|
||||
|
||||
.form-field {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.table-wrapper {
|
||||
background: #fafafa;
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: flex-end;
|
||||
padding: 24px;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
border: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
text-transform: none;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.5px;
|
||||
min-width: 140px;
|
||||
}
|
||||
|
||||
.save-btn {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
color: #616161;
|
||||
}
|
||||
</style>
|
||||
@@ -2,94 +2,93 @@
|
||||
|
||||
// Helper function to safely decode the JWT payload (Access Token or ID Token)
|
||||
const decodeTokenPayload = (token: string | undefined): any | null => {
|
||||
if (!token) return null;
|
||||
try {
|
||||
// Tokens are base64 encoded and separated by '.'
|
||||
const parts = token.split('.');
|
||||
if (parts.length < 2) return null; // Not a valid JWT format
|
||||
|
||||
const payloadBase64 = parts[1];
|
||||
|
||||
// Decode from base64 and parse the JSON
|
||||
// Note: Using Buffer.from is standard in Node.js server environments (like Nitro/H3)
|
||||
return JSON.parse(Buffer.from(payloadBase64, 'base64').toString());
|
||||
} catch (e) {
|
||||
console.error('❌ Failed to decode token payload:', e);
|
||||
return null;
|
||||
}
|
||||
if (!token) return null;
|
||||
try {
|
||||
// Tokens are base64 encoded and separated by '.'
|
||||
const parts = token.split(".");
|
||||
if (parts.length < 2) return null; // Not a valid JWT format
|
||||
|
||||
const payloadBase64 = parts[1];
|
||||
|
||||
// Decode from base64 and parse the JSON
|
||||
// Note: Using Buffer.from is standard in Node.js server environments (like Nitro/H3)
|
||||
return JSON.parse(Buffer.from(payloadBase64, "base64").toString());
|
||||
} catch (e) {
|
||||
console.error("❌ Failed to decode token payload:", e);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// --- START OF THE SINGLE EXPORT DEFAULT HANDLER ---
|
||||
export default defineEventHandler(async (event) => {
|
||||
console.log('🔍 Session endpoint called');
|
||||
console.log("🔍 Session endpoint called");
|
||||
|
||||
const sessionCookie = getCookie(event, 'user_session');
|
||||
console.log('🍪 Session cookie exists:', !!sessionCookie);
|
||||
const sessionCookie = getCookie(event, "user_session");
|
||||
console.log("🍪 Session cookie exists:", !!sessionCookie);
|
||||
|
||||
if (!sessionCookie) {
|
||||
console.log('❌ No session cookie found');
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
statusMessage: 'No session cookie found'
|
||||
});
|
||||
if (!sessionCookie) {
|
||||
console.log("❌ No session cookie found");
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
statusMessage: "No session cookie found",
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const session = JSON.parse(sessionCookie);
|
||||
console.log("📋 Session parsed successfully");
|
||||
|
||||
const isExpired = Date.now() > session.expiresAt;
|
||||
console.log(" Is Expired:", isExpired);
|
||||
|
||||
// Check if the token has expired
|
||||
if (isExpired) {
|
||||
console.log("⏰ Session has expired, clearing cookie");
|
||||
deleteCookie(event, "user_session");
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
statusMessage: "Session expired",
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const session = JSON.parse(sessionCookie);
|
||||
console.log('📋 Session parsed successfully');
|
||||
|
||||
const isExpired = Date.now() > session.expiresAt;
|
||||
console.log(' Is Expired:', isExpired);
|
||||
// Decode tokens and prepare the enhanced response data
|
||||
const idTokenPayload = decodeTokenPayload(session.idToken);
|
||||
const accessTokenPayload = decodeTokenPayload(session.accessToken);
|
||||
|
||||
// Check if the token has expired
|
||||
if (isExpired) {
|
||||
console.log('⏰ Session has expired, clearing cookie');
|
||||
deleteCookie(event, 'user_session');
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
statusMessage: 'Session expired'
|
||||
});
|
||||
}
|
||||
// Final response object for the frontend debug page
|
||||
const sessionResponse = {
|
||||
// Basic User Info
|
||||
user: session.user,
|
||||
|
||||
// Decode tokens and prepare the enhanced response data
|
||||
const idTokenPayload = decodeTokenPayload(session.idToken);
|
||||
const accessTokenPayload = decodeTokenPayload(session.accessToken);
|
||||
|
||||
// Final response object for the frontend debug page
|
||||
const sessionResponse = {
|
||||
// Basic User Info
|
||||
user: session.user,
|
||||
|
||||
// Raw Tokens
|
||||
idToken: session.idToken,
|
||||
accessToken: session.accessToken,
|
||||
refreshToken: session.refreshToken,
|
||||
|
||||
// Session Timestamps
|
||||
expiresAt: session.expiresAt,
|
||||
createdAt: session.createdAt,
|
||||
// Raw Tokens
|
||||
idToken: session.idToken,
|
||||
accessToken: session.accessToken,
|
||||
refreshToken: session.refreshToken,
|
||||
|
||||
// Parsed Payloads
|
||||
idTokenPayload: idTokenPayload,
|
||||
accessTokenPayload: accessTokenPayload,
|
||||
// Session Timestamps
|
||||
expiresAt: session.expiresAt,
|
||||
createdAt: session.createdAt,
|
||||
|
||||
// Raw Session Data (for Debug section)
|
||||
fullSessionObject: session,
|
||||
|
||||
status: 'authenticated',
|
||||
};
|
||||
// Parsed Payloads
|
||||
idTokenPayload: idTokenPayload,
|
||||
accessTokenPayload: accessTokenPayload,
|
||||
|
||||
console.log('✅ Session is valid, returning full session data');
|
||||
return sessionResponse;
|
||||
// Raw Session Data (for Debug section)
|
||||
fullSessionObject: session,
|
||||
|
||||
} catch (parseError) {
|
||||
console.error('❌ Failed to parse session cookie:', parseError);
|
||||
// If JSON parsing fails or any other error occurs, the session is invalid
|
||||
deleteCookie(event, 'user_session');
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
statusMessage: 'Invalid session data'
|
||||
});
|
||||
}
|
||||
status: "authenticated",
|
||||
};
|
||||
|
||||
console.log("✅ Session is valid, returning full session data");
|
||||
return sessionResponse;
|
||||
} catch (parseError) {
|
||||
console.error("❌ Failed to parse session cookie:", parseError);
|
||||
// If JSON parsing fails or any other error occurs, the session is invalid
|
||||
deleteCookie(event, "user_session");
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
statusMessage: "Invalid session data",
|
||||
});
|
||||
}
|
||||
});
|
||||
// --- END OF THE SINGLE EXPORT DEFAULT HANDLER ---
|
||||
// --- END OF THE SINGLE EXPORT DEFAULT HANDLER ---
|
||||
@@ -2,14 +2,18 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { useLocalStorage } from '@vueuse/core';
|
||||
import { computed } from 'vue'; // Import computed dari Vue
|
||||
import { computed } from 'vue'; // Import computed dari Vue
|
||||
|
||||
interface NavItem {
|
||||
id: number;
|
||||
name: string; // Menggantikan 'title'
|
||||
path: string; // Menggantikan 'to'
|
||||
name: string; // Menggantikan 'title'
|
||||
path: string; // Menggantikan 'to'
|
||||
icon: string;
|
||||
children?: NavItem[];
|
||||
badge?: string; // Tambahkan properti badge
|
||||
badge?: string; // Tambahkan properti badge
|
||||
}
|
||||
|
||||
// Initial default navigation items
|
||||
@@ -29,6 +33,9 @@ const defaultNavItems: NavItem[] = [
|
||||
{ id: 7, name: "Buat Antrean", icon: "mdi-account-multiple-plus-outline", path: "/BuatAntrean" },
|
||||
{ id: 8, name: "Monitoring Pasien", icon: "mdi-account-group-outline", path: "/MonitoringPasien/monitoringPasien" },
|
||||
{
|
||||
id: 9,
|
||||
name: "Layar Informasi",
|
||||
icon: "mdi-monitor-multiple",
|
||||
id: 9,
|
||||
name: "Layar Informasi",
|
||||
icon: "mdi-monitor-multiple",
|
||||
@@ -36,8 +43,14 @@ const defaultNavItems: NavItem[] = [
|
||||
children: [
|
||||
{ id: 10, name: "Anjungan", path: "/anjungan/anjungan", icon: "mdi-circle-small" },
|
||||
{ id: 11, name: "Klinik Ruang", path: "/anjungan/AntrianKlinik", icon: "mdi-circle-small" },
|
||||
{ id: 10, name: "Anjungan", path: "/anjungan/anjungan", icon: "mdi-circle-small" },
|
||||
{ id: 11, name: "Klinik Ruang", path: "/anjungan/AntrianKlinik", icon: "mdi-circle-small" },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
name: "Master Data",
|
||||
icon: "mdi-cog-outline",
|
||||
{
|
||||
id: 12,
|
||||
name: "Master Data",
|
||||
@@ -68,6 +81,17 @@ export const useNavItemsStore = defineStore('navItems', () => {
|
||||
});
|
||||
// =============================================================
|
||||
|
||||
// === GETTER PENTING UNTUK MENGATASI MASALAH 'NULL' DI AWAL ===
|
||||
const getNavItems = computed(() => {
|
||||
// Jika navItems.value null, undefined, atau bukan array yang valid,
|
||||
// kembalikan array default.
|
||||
if (!Array.isArray(navItems.value) || navItems.value === null || navItems.value === undefined) {
|
||||
return defaultNavItems;
|
||||
}
|
||||
return navItems.value;
|
||||
});
|
||||
// =============================================================
|
||||
|
||||
function updateNavItems(newItems: NavItem[]) {
|
||||
navItems.value = newItems.map((item, index) => ({
|
||||
...item,
|
||||
@@ -83,6 +107,7 @@ export const useNavItemsStore = defineStore('navItems', () => {
|
||||
return {
|
||||
navItems,
|
||||
getNavItems, // Diekspor untuk digunakan di Sidebar
|
||||
getNavItems, // Diekspor untuk digunakan di Sidebar
|
||||
updateNavItems,
|
||||
addNavItem,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,354 @@
|
||||
// stores/queueStore.js
|
||||
import { defineStore } from 'pinia';
|
||||
import { ref, computed } from 'vue';
|
||||
|
||||
export const useQueueStore = defineStore('queue', () => {
|
||||
// State
|
||||
const allPatients = ref([
|
||||
{
|
||||
no: 1,
|
||||
jamPanggil: "12:49",
|
||||
barcode: "250811100163",
|
||||
noAntrian: "UM1001 | Online - 250811100163",
|
||||
shift: "Shift 1",
|
||||
klinik: "KANDUNGAN",
|
||||
fastTrack: "UMUM",
|
||||
pembayaran: "UMUM",
|
||||
status: "waiting", // waiting, di-loket, terlambat, pending, processed
|
||||
processStage: "loket", // loket, klinik, penunjang
|
||||
createdAt: new Date().toISOString(),
|
||||
},
|
||||
{
|
||||
no: 2,
|
||||
jamPanggil: "10:52",
|
||||
barcode: "250811100155",
|
||||
noAntrian: "UM1002 | Online - 250811100155",
|
||||
shift: "Shift 1",
|
||||
klinik: "IPD",
|
||||
fastTrack: "UMUM",
|
||||
pembayaran: "UMUM",
|
||||
status: "waiting",
|
||||
processStage: "loket",
|
||||
createdAt: new Date().toISOString(),
|
||||
},
|
||||
{
|
||||
no: 3,
|
||||
jamPanggil: "09:30",
|
||||
barcode: "250811100200",
|
||||
noAntrian: "UM1003 | Online - 250811100200",
|
||||
shift: "Shift 1",
|
||||
klinik: "SARAF",
|
||||
fastTrack: "UMUM",
|
||||
pembayaran: "UMUM",
|
||||
status: "waiting",
|
||||
processStage: "loket",
|
||||
createdAt: new Date().toISOString(),
|
||||
},
|
||||
{
|
||||
no: 4,
|
||||
jamPanggil: "14:15",
|
||||
barcode: "250811100210",
|
||||
noAntrian: "UM1004 | Online - 250811100210",
|
||||
shift: "Shift 1",
|
||||
klinik: "THT",
|
||||
fastTrack: "UMUM",
|
||||
pembayaran: "UMUM",
|
||||
status: "waiting",
|
||||
processStage: "loket",
|
||||
createdAt: new Date().toISOString(),
|
||||
},
|
||||
]);
|
||||
|
||||
const quotaUsed = ref(5);
|
||||
const currentProcessingPatient = ref({
|
||||
loket: null,
|
||||
klinik: null,
|
||||
penunjang: null,
|
||||
});
|
||||
|
||||
const kliniks = ref([
|
||||
{ id: 1, name: "KANDUNGAN" },
|
||||
{ id: 2, name: "IPD" },
|
||||
{ id: 3, name: "THT" },
|
||||
{ id: 4, name: "SARAF" },
|
||||
{ id: 5, name: "JIWA" },
|
||||
{ id: 6, name: "BEDAH" },
|
||||
{ id: 7, name: "MATA" },
|
||||
{ id: 8, name: "ANAK" },
|
||||
{ id: 9, name: "KULIT" },
|
||||
]);
|
||||
|
||||
const penunjangs = ref([
|
||||
{ id: 1, name: "LABORATORIUM" },
|
||||
{ id: 2, name: "RADIOLOGI" },
|
||||
{ id: 3, name: "USG" },
|
||||
{ id: 4, name: "CT SCAN" },
|
||||
{ id: 5, name: "MRI" },
|
||||
{ id: 6, name: "EKG" },
|
||||
{ id: 7, name: "ENDOSKOPI" },
|
||||
{ id: 8, name: "FISIOTERAPI" },
|
||||
{ id: 9, name: "FARMASI" },
|
||||
]);
|
||||
|
||||
// Computed - Filter berdasarkan process stage dan status
|
||||
const getPatientsByStage = (stage) => {
|
||||
return computed(() => {
|
||||
const patients = allPatients.value.filter(p => p.processStage === stage);
|
||||
return {
|
||||
all: patients,
|
||||
waiting: patients.filter(p => p.status === 'waiting'),
|
||||
diLoket: patients.filter(p => p.status === 'di-loket'),
|
||||
terlambat: patients.filter(p => p.status === 'terlambat'),
|
||||
pending: patients.filter(p => p.status === 'pending'),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
// Total pasien per stage
|
||||
const getTotalPasienByStage = (stage) => {
|
||||
return computed(() =>
|
||||
allPatients.value.filter(p => p.processStage === stage).length
|
||||
);
|
||||
};
|
||||
|
||||
const totalPasien = computed(() => allPatients.value.length);
|
||||
|
||||
// Actions
|
||||
const callNext = (adminType = 'loket') => {
|
||||
const stageMap = {
|
||||
'loket': 'loket',
|
||||
'klinik': 'klinik',
|
||||
'penunjang': 'penunjang'
|
||||
};
|
||||
|
||||
const targetStage = stageMap[adminType];
|
||||
const nextPatient = allPatients.value.find(p =>
|
||||
p.status === 'waiting' && p.processStage === targetStage
|
||||
);
|
||||
|
||||
if (!nextPatient) {
|
||||
return { success: false, message: "Tidak ada pasien selanjutnya" };
|
||||
}
|
||||
|
||||
if (quotaUsed.value >= 150) {
|
||||
return { success: false, message: "Quota sudah penuh" };
|
||||
}
|
||||
|
||||
nextPatient.status = "di-loket";
|
||||
currentProcessingPatient.value[adminType] = nextPatient;
|
||||
quotaUsed.value++;
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Memanggil pasien ${nextPatient.noAntrian.split(" |")[0]}`,
|
||||
};
|
||||
};
|
||||
|
||||
const callMultiplePatients = (count, adminType = 'loket') => {
|
||||
const stageMap = {
|
||||
'loket': 'loket',
|
||||
'klinik': 'klinik',
|
||||
'penunjang': 'penunjang'
|
||||
};
|
||||
|
||||
const targetStage = stageMap[adminType];
|
||||
const waitingList = allPatients.value.filter(p =>
|
||||
p.status === 'waiting' && p.processStage === targetStage
|
||||
);
|
||||
|
||||
const patientsToCall = waitingList.slice(0, count);
|
||||
|
||||
if (patientsToCall.length === 0) {
|
||||
return { success: false, message: "Tidak ada pasien yang menunggu" };
|
||||
}
|
||||
|
||||
if (quotaUsed.value + patientsToCall.length > 150) {
|
||||
return { success: false, message: "Quota tidak mencukupi" };
|
||||
}
|
||||
|
||||
patientsToCall.forEach((patient) => {
|
||||
patient.status = "di-loket";
|
||||
});
|
||||
|
||||
quotaUsed.value += patientsToCall.length;
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Memanggil ${patientsToCall.length} pasien ke loket`,
|
||||
};
|
||||
};
|
||||
|
||||
const processPatient = (patient, action, adminType = 'loket') => {
|
||||
const patientCode = patient.noAntrian.split(" |")[0];
|
||||
let message = "";
|
||||
|
||||
switch (action) {
|
||||
case "check-in":
|
||||
// Jika check-in di loket, pindahkan ke klinik dengan status di-loket
|
||||
if (adminType === 'loket') {
|
||||
patient.status = "di-loket"; // Langsung masuk tabel Di Loket Klinik
|
||||
patient.processStage = "klinik";
|
||||
message = `Pasien ${patientCode} berhasil check in dan masuk ke Tabel Loket Klinik`;
|
||||
}
|
||||
// Jika check-in di klinik, selesai
|
||||
else if (adminType === 'klinik') {
|
||||
patient.status = "processed";
|
||||
message = `Pasien ${patientCode} berhasil check in di Klinik`;
|
||||
}
|
||||
// Jika check-in di penunjang, selesai
|
||||
else if (adminType === 'penunjang') {
|
||||
patient.status = "processed";
|
||||
message = `Pasien ${patientCode} berhasil check in di Penunjang`;
|
||||
}
|
||||
|
||||
// Clear current processing di admin yang melakukan check-in
|
||||
if (currentProcessingPatient.value[adminType]?.no === patient.no) {
|
||||
currentProcessingPatient.value[adminType] = null;
|
||||
}
|
||||
break;
|
||||
|
||||
case "terlambat":
|
||||
patient.status = "terlambat";
|
||||
if (currentProcessingPatient.value[adminType]?.no === patient.no) {
|
||||
currentProcessingPatient.value[adminType] = null;
|
||||
}
|
||||
message = `Pasien ${patientCode} ditandai terlambat`;
|
||||
break;
|
||||
|
||||
case "pending":
|
||||
patient.status = "pending";
|
||||
if (currentProcessingPatient.value[adminType]?.no === patient.no) {
|
||||
currentProcessingPatient.value[adminType] = null;
|
||||
}
|
||||
message = `Pasien ${patientCode} di-pending`;
|
||||
break;
|
||||
|
||||
case "aktifkan":
|
||||
if (patient.status === "terlambat" || patient.status === "pending") {
|
||||
patient.status = "di-loket";
|
||||
message = `Pasien ${patientCode} diaktifkan kembali`;
|
||||
}
|
||||
break;
|
||||
|
||||
case "proses":
|
||||
currentProcessingPatient.value[adminType] = patient;
|
||||
message = `Memproses pasien ${patientCode}`;
|
||||
break;
|
||||
}
|
||||
|
||||
return { success: true, message };
|
||||
};
|
||||
|
||||
const createAntreanKlinik = (klinik, adminType = 'loket') => {
|
||||
const newNo = allPatients.value.length + 1;
|
||||
const timestamp = new Date();
|
||||
const barcode = `250811${String(timestamp.getTime()).slice(-6)}`;
|
||||
|
||||
const newPatient = {
|
||||
no: newNo,
|
||||
jamPanggil: `${String(timestamp.getHours()).padStart(2, "0")}:${String(
|
||||
timestamp.getMinutes()
|
||||
).padStart(2, "0")}`,
|
||||
barcode: barcode,
|
||||
noAntrian: `KL${String(newNo).padStart(4, "0")} | Klinik - ${barcode}`,
|
||||
shift: "Shift 1",
|
||||
klinik: klinik.name,
|
||||
fastTrack: "UMUM",
|
||||
pembayaran: "UMUM",
|
||||
status: "di-loket", // Langsung masuk tabel Di Loket Klinik
|
||||
processStage: "klinik",
|
||||
createdAt: timestamp.toISOString(),
|
||||
};
|
||||
|
||||
allPatients.value.push(newPatient);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Antrean klinik ${klinik.name} berhasil dibuat dan masuk ke Tabel Loket Klinik`,
|
||||
patient: newPatient,
|
||||
};
|
||||
};
|
||||
|
||||
const createAntreanPenunjang = (penunjang, patient = null, adminType = 'loket') => {
|
||||
const newNo = allPatients.value.length + 1;
|
||||
const timestamp = new Date();
|
||||
const barcode = patient ? patient.barcode : `250811${String(timestamp.getTime()).slice(-6)}`;
|
||||
|
||||
const newPatient = {
|
||||
no: newNo,
|
||||
jamPanggil: `${String(timestamp.getHours()).padStart(2, "0")}:${String(
|
||||
timestamp.getMinutes()
|
||||
).padStart(2, "0")}`,
|
||||
barcode: barcode,
|
||||
noAntrian: `PN${String(newNo).padStart(4, "0")} | Penunjang - ${barcode}`,
|
||||
shift: "Shift 1",
|
||||
klinik: penunjang.name,
|
||||
fastTrack: "UMUM",
|
||||
pembayaran: patient ? patient.pembayaran : "UMUM",
|
||||
status: "di-loket", // Langsung masuk tabel Di Loket Penunjang
|
||||
processStage: "penunjang",
|
||||
createdAt: timestamp.toISOString(),
|
||||
referencePatient: patient ? patient.noAntrian : null,
|
||||
};
|
||||
|
||||
allPatients.value.push(newPatient);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Antrean penunjang ${penunjang.name} berhasil dibuat dan masuk ke Tabel Loket Penunjang`,
|
||||
patient: newPatient,
|
||||
};
|
||||
};
|
||||
|
||||
const changeKlinik = (patient, newKlinik, adminType = 'loket') => {
|
||||
const patientIndex = allPatients.value.findIndex((p) => p.no === patient.no);
|
||||
|
||||
if (patientIndex !== -1) {
|
||||
allPatients.value[patientIndex].klinik = newKlinik.name;
|
||||
|
||||
// Update current processing if it's the same patient
|
||||
if (currentProcessingPatient.value[adminType]?.no === patient.no) {
|
||||
currentProcessingPatient.value[adminType].klinik = newKlinik.name;
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Klinik berhasil diubah ke ${newKlinik.name}`,
|
||||
};
|
||||
}
|
||||
|
||||
return { success: false, message: "Pasien tidak ditemukan" };
|
||||
};
|
||||
|
||||
const getCurrentProcessing = (adminType) => {
|
||||
return computed(() => currentProcessingPatient.value[adminType]);
|
||||
};
|
||||
|
||||
const setCurrentProcessing = (patient, adminType) => {
|
||||
currentProcessingPatient.value[adminType] = patient;
|
||||
};
|
||||
|
||||
return {
|
||||
// State
|
||||
allPatients,
|
||||
quotaUsed,
|
||||
currentProcessingPatient,
|
||||
kliniks,
|
||||
penunjangs,
|
||||
|
||||
// Computed
|
||||
totalPasien,
|
||||
|
||||
// Actions
|
||||
callNext,
|
||||
callMultiplePatients,
|
||||
processPatient,
|
||||
createAntreanKlinik,
|
||||
createAntreanPenunjang,
|
||||
changeKlinik,
|
||||
getPatientsByStage,
|
||||
getTotalPasienByStage,
|
||||
getCurrentProcessing,
|
||||
setCurrentProcessing,
|
||||
};
|
||||
});
|
||||
Reference in New Issue
Block a user