542 lines
12 KiB
Vue
542 lines
12 KiB
Vue
<template>
|
|
<div class="loket-preview-container">
|
|
<!-- Loading State -->
|
|
<div v-if="isLoading" class="loading-overlay">
|
|
<v-progress-circular indeterminate color="primary" size="60"></v-progress-circular>
|
|
<p class="mt-4">Memuat data loket...</p>
|
|
</div>
|
|
|
|
<!-- Header -->
|
|
<div v-else class="preview-header">
|
|
<div class="header-left">
|
|
<div class="logo-circle">
|
|
<v-icon size="40" color="primary">mdi-hospital-box</v-icon>
|
|
</div>
|
|
<div class="header-text">
|
|
<h1 class="hospital-name">ANTRIAN LOKET</h1>
|
|
<p class="display-subtitle">RSUD dr. Saiful Anwar Provinsi Jawa Timur</p>
|
|
<p v-if="loketData" class="loket-info">{{ loketData.namaLoket }}</p>
|
|
</div>
|
|
</div>
|
|
<div class="header-right">
|
|
<div class="datetime-display">
|
|
<div class="time-large">{{ currentTime }}</div>
|
|
<div class="date-small">{{ currentDate }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Main Grid - Display Klinik -->
|
|
<div v-if="!isLoading" class="kliniks-grid">
|
|
<div
|
|
v-for="klinik in displayedClinics"
|
|
:key="klinik.name"
|
|
class="klinik-box"
|
|
>
|
|
<!-- Klinik Header -->
|
|
<div class="klinik-header-bar">
|
|
<span class="klinik-title">{{ klinik.name }}</span>
|
|
<v-chip size="small" class="klinik-count">
|
|
<v-icon size="16" color="white" class="mr-1">mdi-account-multiple</v-icon>
|
|
<span class="count-text">{{ klinik.totalQueues }}</span>
|
|
</v-chip>
|
|
</div>
|
|
|
|
<!-- Queue Content -->
|
|
<div class="klinik-content">
|
|
<!-- Current Serving Queue (Large) -->
|
|
<div class="current-serving-section">
|
|
<div class="current-label">SEDANG DILAYANI</div>
|
|
<div
|
|
v-if="klinik.currentQueue"
|
|
class="current-number-large"
|
|
>
|
|
{{ klinik.currentQueue.noAntrian.split(' |')[0] }}
|
|
</div>
|
|
<div v-else class="current-waiting-text">
|
|
MENUNGGU PANGGILAN
|
|
</div>
|
|
</div>
|
|
|
|
<!-- All Queues Grid (Small) -->
|
|
<div
|
|
v-if="klinik.allQueues && klinik.allQueues.length > 0"
|
|
class="all-queues-grid"
|
|
:style="getGridStyle(klinik.allQueues.length)"
|
|
>
|
|
<div
|
|
v-for="queue in klinik.allQueues"
|
|
:key="`queue-${queue.no}`"
|
|
class="queue-grid-item"
|
|
:class="{
|
|
'is-current': queue.no === klinik.currentQueue?.no,
|
|
}"
|
|
>
|
|
{{ queue.noAntrian.split(' |')[0] }}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Empty State -->
|
|
<div v-if="!klinik.currentQueue && (!klinik.allQueues || klinik.allQueues.length === 0)" class="empty-state">
|
|
<v-icon size="48" color="grey-lighten-3">mdi-clock-outline</v-icon>
|
|
<p class="empty-text">Tidak Ada Antrian</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, computed, onMounted } from 'vue'
|
|
import { useLoketStore } from '@/stores/loketStore'
|
|
|
|
const props = defineProps({
|
|
loketId: {
|
|
type: Number,
|
|
required: true
|
|
}
|
|
})
|
|
|
|
const loketStore = useLoketStore()
|
|
|
|
const currentTime = ref('')
|
|
const currentDate = ref('')
|
|
const isLoading = ref(true)
|
|
let timeInterval = null
|
|
|
|
// Get loket data
|
|
const loketData = computed(() => {
|
|
return loketStore.getLoketById(props.loketId) || null
|
|
})
|
|
|
|
// Mock queue data untuk preview
|
|
const mockQueues = ref([
|
|
{ no: 1, noAntrian: 'RT001', klinik: 'R. TINDAKAN', status: 'current' },
|
|
{ no: 2, noAntrian: 'RT002', klinik: 'R. TINDAKAN', status: 'anjungan' },
|
|
{ no: 3, noAntrian: 'RD001', klinik: 'RADIOTERAPI', status: 'anjungan' },
|
|
{ no: 4, noAntrian: 'RM001', klinik: 'REHAB MEDIK', status: 'anjungan' },
|
|
])
|
|
|
|
// Simulasi klinik dengan queue
|
|
const displayedClinics = computed(() => {
|
|
// Gunakan pelayanan dari loketData jika available, atau gunakan contoh default
|
|
const pelayanan = loketData.value?.pelayanan || ['RT', 'RD', 'RM']
|
|
|
|
// Map pelayanan ke nama klinik untuk preview
|
|
const klinikNames = {
|
|
'RT': 'R. TINDAKAN',
|
|
'RD': 'RADIOTERAPI',
|
|
'RM': 'REHAB MEDIK',
|
|
'RA': 'RADIOTERAPI',
|
|
'IP': 'RAWAT INAP',
|
|
'RJ': 'RAWAT JALAN',
|
|
}
|
|
|
|
const clinicsMap = new Map()
|
|
|
|
// Create mock clinic entries berdasarkan pelayanan
|
|
pelayanan.forEach(kode => {
|
|
const klinikName = klinikNames[kode] || kode
|
|
if (!clinicsMap.has(klinikName)) {
|
|
clinicsMap.set(klinikName, {
|
|
name: klinikName,
|
|
code: kode,
|
|
allQueues: [],
|
|
currentQueue: null,
|
|
totalQueues: 0
|
|
})
|
|
}
|
|
})
|
|
|
|
// Distribute mock queues
|
|
mockQueues.value.forEach(queue => {
|
|
const klinikEntry = clinicsMap.get(queue.klinik)
|
|
if (klinikEntry) {
|
|
klinikEntry.allQueues.push(queue)
|
|
if (queue.status === 'current') {
|
|
klinikEntry.currentQueue = queue
|
|
}
|
|
}
|
|
})
|
|
|
|
// Calculate totals
|
|
clinicsMap.forEach(clinic => {
|
|
clinic.totalQueues = clinic.allQueues.length
|
|
})
|
|
|
|
return Array.from(clinicsMap.values())
|
|
})
|
|
|
|
const getGridStyle = (queueCount) => {
|
|
if (queueCount <= 3) return { gridTemplateColumns: `repeat(${queueCount}, 1fr)` }
|
|
if (queueCount <= 6) return { gridTemplateColumns: 'repeat(3, 1fr)' }
|
|
return { gridTemplateColumns: 'repeat(4, 1fr)' }
|
|
}
|
|
|
|
const updateTime = () => {
|
|
const now = new Date()
|
|
currentTime.value = now.toLocaleTimeString('id-ID', {
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
second: '2-digit'
|
|
})
|
|
|
|
currentDate.value = now.toLocaleDateString('id-ID', {
|
|
weekday: 'long',
|
|
day: 'numeric',
|
|
month: 'long',
|
|
year: 'numeric'
|
|
})
|
|
}
|
|
|
|
onMounted(async () => {
|
|
try {
|
|
// Fetch loket data dari API terlebih dahulu
|
|
console.log('📊 [LoketPreview] Fetching loket data for ID:', props.loketId);
|
|
const result = await loketStore.fetchLoketFromAPI(true)
|
|
|
|
if (result.success) {
|
|
console.log('✅ [LoketPreview] Loket data fetched successfully:', result.message);
|
|
} else {
|
|
console.warn('⚠️ [LoketPreview] Failed to fetch loket data:', result.message);
|
|
}
|
|
} catch (error) {
|
|
console.error('❌ [LoketPreview] Error fetching loket data:', error);
|
|
} finally {
|
|
isLoading.value = false
|
|
// Start timer setelah data di-fetch
|
|
updateTime()
|
|
timeInterval = setInterval(updateTime, 1000)
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
// Color variables
|
|
$primary-600: #3A61C9;
|
|
$primary-700: #3556AE;
|
|
$primary-50: #F0F4FF;
|
|
$primary-200: #9AB2F1;
|
|
$neutral-100: #FFFFFF;
|
|
$neutral-300: #F5F7FA;
|
|
$neutral-600: #89939E;
|
|
$neutral-700: #717171;
|
|
$neutral-900: #212121;
|
|
$warning-600: #FF9800;
|
|
$warning-700: #F57C00;
|
|
$warning-100: #FFF3E0;
|
|
|
|
.loket-preview-container {
|
|
background: $neutral-300;
|
|
padding: 24px;
|
|
font-family: 'Inter', 'Roboto', sans-serif;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 20px;
|
|
min-height: 100%;
|
|
position: relative;
|
|
}
|
|
|
|
/* ========== LOADING STATE ========== */
|
|
.loading-overlay {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background: rgba(255, 255, 255, 0.95);
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
z-index: 100;
|
|
border-radius: 8px;
|
|
backdrop-filter: blur(5px);
|
|
}
|
|
|
|
.loading-overlay p {
|
|
color: $neutral-700;
|
|
font-size: 16px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
/* ========== HEADER ========== */
|
|
.preview-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
background: linear-gradient(135deg, $primary-600 0%, $primary-700 100%);
|
|
border-radius: 16px;
|
|
padding: 24px 40px;
|
|
box-shadow: 0 8px 24px rgba(33, 150, 243, 0.3);
|
|
}
|
|
|
|
.header-left {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 24px;
|
|
}
|
|
|
|
.logo-circle {
|
|
background: rgba(255, 255, 255, 0.95);
|
|
width: 80px;
|
|
height: 80px;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
backdrop-filter: blur(10px);
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.hospital-name {
|
|
font-size: 48px;
|
|
font-weight: 800;
|
|
color: $neutral-100;
|
|
margin: 0;
|
|
letter-spacing: 2px;
|
|
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
|
line-height: 1;
|
|
}
|
|
|
|
.display-subtitle {
|
|
font-size: 20px;
|
|
font-weight: 600;
|
|
color: $neutral-100;
|
|
margin: 6px 0 0 0;
|
|
opacity: 0.95;
|
|
}
|
|
|
|
.loket-info {
|
|
font-size: 16px;
|
|
font-weight: 500;
|
|
color: $neutral-100;
|
|
margin: 4px 0 0 0;
|
|
opacity: 0.85;
|
|
}
|
|
|
|
.header-right {
|
|
text-align: right;
|
|
}
|
|
|
|
.datetime-display {
|
|
text-align: right;
|
|
}
|
|
|
|
.time-large {
|
|
font-size: 56px;
|
|
font-weight: 900;
|
|
color: $neutral-100;
|
|
letter-spacing: 2px;
|
|
line-height: 1;
|
|
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
|
}
|
|
|
|
.date-small {
|
|
font-size: 18px;
|
|
color: $neutral-100;
|
|
margin-top: 8px;
|
|
font-weight: 500;
|
|
opacity: 0.95;
|
|
}
|
|
|
|
/* ========== KLINIKS GRID ========== */
|
|
.kliniks-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
|
|
gap: 20px;
|
|
flex: 1;
|
|
}
|
|
|
|
.klinik-box {
|
|
background: $neutral-100;
|
|
border: 2px solid $primary-200;
|
|
border-radius: 16px;
|
|
overflow: hidden;
|
|
box-shadow: 0 4px 12px rgba(33, 150, 243, 0.12);
|
|
min-height: 320px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.klinik-header-bar {
|
|
background: linear-gradient(135deg, $primary-600 0%, $primary-700 100%);
|
|
padding: 18px 22px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.klinik-title {
|
|
font-size: 24px;
|
|
font-weight: 800;
|
|
color: $neutral-100;
|
|
letter-spacing: 0.5px;
|
|
text-transform: uppercase;
|
|
line-height: 1.2;
|
|
flex: 1;
|
|
}
|
|
|
|
.klinik-count {
|
|
background: rgba(255, 255, 255, 0.2) !important;
|
|
color: $neutral-100 !important;
|
|
font-weight: 700;
|
|
backdrop-filter: blur(10px);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
padding: 6px 12px !important;
|
|
}
|
|
|
|
.count-text {
|
|
font-size: 18px;
|
|
font-weight: 800;
|
|
}
|
|
|
|
.klinik-content {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
padding: 22px;
|
|
background: $primary-50;
|
|
gap: 12px;
|
|
}
|
|
|
|
/* Current Serving Section */
|
|
.current-serving-section {
|
|
text-align: center;
|
|
}
|
|
|
|
.current-label {
|
|
font-size: 16px;
|
|
font-weight: 700;
|
|
color: $primary-600;
|
|
margin-bottom: 12px;
|
|
letter-spacing: 1px;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.current-number-large {
|
|
font-size: 80px;
|
|
font-weight: 900;
|
|
color: $neutral-900;
|
|
letter-spacing: 4px;
|
|
line-height: 1;
|
|
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.current-waiting-text {
|
|
font-size: 32px;
|
|
font-weight: 700;
|
|
color: $neutral-600;
|
|
letter-spacing: 1px;
|
|
padding: 40px 0;
|
|
opacity: 0.7;
|
|
}
|
|
|
|
/* All Queues Grid */
|
|
.all-queues-grid {
|
|
display: grid;
|
|
gap: 10px;
|
|
margin-top: 12px;
|
|
}
|
|
|
|
.queue-grid-item {
|
|
background: $neutral-100;
|
|
border: 2px solid $neutral-600;
|
|
border-radius: 8px;
|
|
padding: 10px 8px;
|
|
text-align: center;
|
|
font-size: 16px;
|
|
font-weight: 700;
|
|
color: $neutral-700;
|
|
transition: all 0.3s ease;
|
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
|
|
&.is-current {
|
|
background: $warning-100;
|
|
border: 3px solid $warning-600;
|
|
color: $warning-700;
|
|
font-weight: 800;
|
|
box-shadow: 0 2px 6px rgba(255, 152, 0, 0.3);
|
|
}
|
|
}
|
|
|
|
.empty-state {
|
|
text-align: center;
|
|
padding: 32px 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.empty-text {
|
|
font-size: 16px;
|
|
color: $neutral-600;
|
|
margin-top: 12px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* Responsive */
|
|
@media (max-width: 1366px) {
|
|
.hospital-name {
|
|
font-size: 36px;
|
|
}
|
|
|
|
.time-large {
|
|
font-size: 48px;
|
|
}
|
|
|
|
.klinik-title {
|
|
font-size: 20px;
|
|
}
|
|
|
|
.current-number-large {
|
|
font-size: 72px;
|
|
}
|
|
|
|
.current-waiting-text {
|
|
font-size: 24px;
|
|
padding: 30px 0;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.loket-preview-container {
|
|
padding: 16px;
|
|
}
|
|
|
|
.preview-header {
|
|
flex-direction: column;
|
|
text-align: center;
|
|
gap: 16px;
|
|
}
|
|
|
|
.header-left {
|
|
flex-direction: column;
|
|
}
|
|
|
|
.header-right {
|
|
width: 100%;
|
|
}
|
|
|
|
.kliniks-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.hospital-name {
|
|
font-size: 28px;
|
|
}
|
|
|
|
.time-large {
|
|
font-size: 36px;
|
|
}
|
|
|
|
.current-number-large {
|
|
font-size: 60px;
|
|
}
|
|
}
|
|
</style>
|