|
|
|
@@ -1,6 +1,5 @@
|
|
|
|
|
<template>
|
|
|
|
|
<div class="scaler-wrapper">
|
|
|
|
|
<div class="antrian-display-container">
|
|
|
|
|
<div class="antrian-display-container">
|
|
|
|
|
<!-- Header -->
|
|
|
|
|
<div class="display-header">
|
|
|
|
|
<div class="header-left">
|
|
|
|
@@ -17,7 +16,7 @@
|
|
|
|
|
<div class="header-text">
|
|
|
|
|
<h1 class="hospital-name">ANTRIAN KLINIK RUANG</h1>
|
|
|
|
|
<p class="display-subtitle">RSUD dr. Saiful Anwar Provinsi Jawa Timur</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="header-right">
|
|
|
|
|
<div class="datetime-display">
|
|
|
|
@@ -38,11 +37,12 @@
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Main Grid - Display Ruang -->
|
|
|
|
|
<div class="clinics-grid">
|
|
|
|
|
<div class="clinics-grid" :style="gridStyle">
|
|
|
|
|
<div
|
|
|
|
|
v-for="ruang in displayedRuang"
|
|
|
|
|
:key="`${ruang.kodeKlinik}-${ruang.nomorRuang}`"
|
|
|
|
|
class="clinic-box"
|
|
|
|
|
:class="{ 'is-single-view': displayedRuang.length === 1 }"
|
|
|
|
|
>
|
|
|
|
|
<!-- Clinic Header -->
|
|
|
|
|
<div class="clinic-header-bar">
|
|
|
|
@@ -133,7 +133,6 @@
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
@@ -147,6 +146,16 @@ definePageMeta({
|
|
|
|
|
layout: false,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Set viewport meta tag for proper responsive behavior
|
|
|
|
|
useHead({
|
|
|
|
|
meta: [
|
|
|
|
|
{
|
|
|
|
|
name: 'viewport',
|
|
|
|
|
content: 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover'
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const route = useRoute()
|
|
|
|
|
const queueStore = useQueueStore()
|
|
|
|
|
const masterStore = useMasterStore()
|
|
|
|
@@ -290,46 +299,27 @@ const displayedRuang = computed(() => {
|
|
|
|
|
const priorityDiff = (statusPriority[a.status] || 99) - (statusPriority[b.status] || 99)
|
|
|
|
|
if (priorityDiff !== 0) return priorityDiff
|
|
|
|
|
|
|
|
|
|
// Sort by queue number
|
|
|
|
|
const numA = parseInt(a.noAntrian.match(/\d+/)?.[0] || '999')
|
|
|
|
|
const numB = parseInt(b.noAntrian.match(/\d+/)?.[0] || '999')
|
|
|
|
|
const numA = parseInt(a.noAntrian.replace(/\D/g, '') || '999')
|
|
|
|
|
const numB = parseInt(b.noAntrian.replace(/\D/g, '') || '999')
|
|
|
|
|
return numA - numB
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Split by tipeLayanan berdasarkan tipeLayanan terakhir yang dipanggil
|
|
|
|
|
// Pasien yang dipanggil akan muncul di kolom sesuai tipeLayanan terakhir yang dipanggil
|
|
|
|
|
// Jika pasien dipanggil untuk Pemeriksaan Awal, muncul di kolom Pemeriksaan Awal
|
|
|
|
|
// Jika pasien dipanggil untuk Tindakan, muncul di kolom Tindakan (dan hilang dari Pemeriksaan Awal)
|
|
|
|
|
const pemeriksaanAwalQueues = allQueues.filter(q => {
|
|
|
|
|
// Gunakan tipeLayanan (yang diupdate saat dipanggil) untuk menentukan kolom
|
|
|
|
|
return q.tipeLayanan === 'Pemeriksaan Awal';
|
|
|
|
|
});
|
|
|
|
|
const tindakanQueues = allQueues.filter(q => {
|
|
|
|
|
// Gunakan tipeLayanan (yang diupdate saat dipanggil) untuk menentukan kolom
|
|
|
|
|
return q.tipeLayanan === 'Tindakan';
|
|
|
|
|
})
|
|
|
|
|
const pemeriksaanAwalQueues = allQueues.filter(q => q.tipeLayanan === 'Pemeriksaan Awal')
|
|
|
|
|
const tindakanQueues = allQueues.filter(q => q.tipeLayanan === 'Tindakan')
|
|
|
|
|
|
|
|
|
|
// Get current and next queues for each tipeLayanan
|
|
|
|
|
// Current queue is the one with status 'di-loket' and matching tipeLayanan
|
|
|
|
|
// Jika ada pasien baru yang dipanggil, akan menggantikan pasien sebelumnya di kolom tersebut
|
|
|
|
|
const getCurrentAndNext = (queues, tipeLayanan) => {
|
|
|
|
|
// Find current: pasien dengan status 'di-loket' dan tipeLayanan yang sesuai
|
|
|
|
|
// Sort by lastCalledAt untuk mendapatkan yang terakhir dipanggil (yang baru dipanggil akan menggantikan yang lama)
|
|
|
|
|
const currentCandidates = queues.filter(q =>
|
|
|
|
|
q.status === 'di-loket' && q.tipeLayanan === tipeLayanan
|
|
|
|
|
).sort((a, b) => {
|
|
|
|
|
const timeA = a.lastCalledAt ? new Date(a.lastCalledAt) : new Date(a.createdAt || 0)
|
|
|
|
|
const timeB = b.lastCalledAt ? new Date(b.lastCalledAt) : new Date(b.createdAt || 0)
|
|
|
|
|
return timeB - timeA // Yang terakhir dipanggil di atas
|
|
|
|
|
const timeA = a.lastCalledAt ? new Date(a.lastCalledAt).getTime() : 0
|
|
|
|
|
const timeB = b.lastCalledAt ? new Date(b.lastCalledAt).getTime() : 0
|
|
|
|
|
return timeB - timeA
|
|
|
|
|
})
|
|
|
|
|
// Ambil yang terakhir dipanggil sebagai current (menggantikan yang sebelumnya)
|
|
|
|
|
const current = currentCandidates.length > 0 ? currentCandidates[0] : null
|
|
|
|
|
|
|
|
|
|
// Next queues: exclude current patient dan pasien dengan status 'di-loket' (yang sudah dipanggil)
|
|
|
|
|
const current = currentCandidates.length > 0 ? currentCandidates[0] : null
|
|
|
|
|
const next = queues.filter(q =>
|
|
|
|
|
q.no !== current?.no &&
|
|
|
|
|
q.status === 'waiting' // Hanya yang waiting (belum dipanggil)
|
|
|
|
|
).slice(0, 3) // Show max 3 next queues
|
|
|
|
|
q.no !== current?.no && q.status === 'waiting'
|
|
|
|
|
).slice(0, 3)
|
|
|
|
|
return { current, next }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -394,6 +384,19 @@ const statistics = computed(() => {
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Dynamic grid logic: divide total by 2 to balance across rows, cap at 5 for larger cards
|
|
|
|
|
// Meniru logika AntrianLoket
|
|
|
|
|
const gridColumns = computed(() => {
|
|
|
|
|
const total = displayedRuang.value.length
|
|
|
|
|
if (total <= 1) return 1
|
|
|
|
|
if (total <= 5) return total
|
|
|
|
|
return Math.min(5, Math.ceil(total / 2))
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const gridStyle = computed(() => ({
|
|
|
|
|
gridTemplateColumns: `repeat(${gridColumns.value}, 1fr)`
|
|
|
|
|
}))
|
|
|
|
|
|
|
|
|
|
const updateTime = () => {
|
|
|
|
|
const now = new Date()
|
|
|
|
|
// Format jam dengan titik dua (HH:MM:SS)
|
|
|
|
@@ -460,9 +463,10 @@ const initWebSocket = () => {
|
|
|
|
|
console.log('🆔 Client ID:', anjunganClientId.value)
|
|
|
|
|
|
|
|
|
|
wsInstance = useWebSocket({
|
|
|
|
|
url: wsBaseUrl,
|
|
|
|
|
clientId: anjunganClientId.value,
|
|
|
|
|
onMessage: (data) => {
|
|
|
|
|
url: wsBaseUrl,
|
|
|
|
|
clientId: anjunganClientId.value,
|
|
|
|
|
fallbackPostUrl: '/stats-api/ws',
|
|
|
|
|
onMessage: (data) => {
|
|
|
|
|
console.log('📨 WebSocket raw message received:', data)
|
|
|
|
|
console.log('📨 Message type:', typeof data)
|
|
|
|
|
|
|
|
|
@@ -562,33 +566,6 @@ const initWebSocket = () => {
|
|
|
|
|
return wsInstance
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Responsive scaling function
|
|
|
|
|
const updateScale = () => {
|
|
|
|
|
const container = document.querySelector('.antrian-display-container')
|
|
|
|
|
if (!container) return
|
|
|
|
|
|
|
|
|
|
const viewportWidth = window.innerWidth
|
|
|
|
|
const viewportHeight = window.innerHeight
|
|
|
|
|
const baseWidth = 1920
|
|
|
|
|
const baseHeight = 1080
|
|
|
|
|
|
|
|
|
|
// Calculate scale factors
|
|
|
|
|
const scaleX = viewportWidth / baseWidth
|
|
|
|
|
const scaleY = viewportHeight / baseHeight
|
|
|
|
|
|
|
|
|
|
// Use smaller scale to fit both dimensions
|
|
|
|
|
const scale = Math.min(scaleX, scaleY)
|
|
|
|
|
|
|
|
|
|
// Apply scale using CSS variable
|
|
|
|
|
container.style.setProperty('--scale-factor', scale.toString())
|
|
|
|
|
|
|
|
|
|
// Adjust container dimensions for smaller screens
|
|
|
|
|
if (viewportWidth < baseWidth || viewportHeight < baseHeight) {
|
|
|
|
|
container.style.width = `${baseWidth}px`
|
|
|
|
|
container.style.height = `${baseHeight}px`
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
// Redirect to index if klinik not found
|
|
|
|
|
if (!klinikData.value) {
|
|
|
|
@@ -607,10 +584,6 @@ onMounted(() => {
|
|
|
|
|
console.log('🚀 WebSocket connection initiated')
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Setup responsive scaling
|
|
|
|
|
updateScale()
|
|
|
|
|
window.addEventListener('resize', updateScale)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
onUnmounted(() => {
|
|
|
|
@@ -619,7 +592,6 @@ onUnmounted(() => {
|
|
|
|
|
wsInstance.disconnect()
|
|
|
|
|
console.log('🔌 WebSocket disconnected')
|
|
|
|
|
}
|
|
|
|
|
window.removeEventListener('resize', updateScale)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Watch for clientId changes and reconnect if needed
|
|
|
|
@@ -636,36 +608,16 @@ watch(anjunganClientId, (newClientId, oldClientId) => {
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
|
|
.scaler-wrapper {
|
|
|
|
|
position: fixed;
|
|
|
|
|
top: 0;
|
|
|
|
|
left: 0;
|
|
|
|
|
width: 100vw;
|
|
|
|
|
height: 100vh;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
background: var(--color-neutral-300);
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
z-index: 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.antrian-display-container {
|
|
|
|
|
background: var(--color-neutral-300);
|
|
|
|
|
width: 1920px;
|
|
|
|
|
height: 1080px;
|
|
|
|
|
width: 100vw;
|
|
|
|
|
height: 100vh;
|
|
|
|
|
padding: 24px;
|
|
|
|
|
font-family: 'Inter', 'Roboto', sans-serif;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
flex-shrink: 0; /* Prevent flex from shrinking the fixed-size container */
|
|
|
|
|
|
|
|
|
|
/* Responsive scaling menggunakan CSS variables dan JavaScript */
|
|
|
|
|
transform-origin: center center;
|
|
|
|
|
--scale-factor: 1;
|
|
|
|
|
transform: scale(var(--scale-factor));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ========== HEADER ========== */
|
|
|
|
@@ -723,14 +675,6 @@ watch(anjunganClientId, (newClientId, oldClientId) => {
|
|
|
|
|
opacity: 0.95;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.klinik-info {
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
color: var(--color-neutral-100);
|
|
|
|
|
margin: 4px 0 0 0;
|
|
|
|
|
opacity: 0.85;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.datetime-display {
|
|
|
|
|
text-align: right;
|
|
|
|
|
}
|
|
|
|
@@ -796,10 +740,11 @@ watch(anjunganClientId, (newClientId, oldClientId) => {
|
|
|
|
|
/* ========== CLINICS GRID ========== */
|
|
|
|
|
.clinics-grid {
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: repeat(3, 1fr);
|
|
|
|
|
/* grid-template-columns set via inline style */
|
|
|
|
|
gap: 16px;
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
flex: 1;
|
|
|
|
|
min-height: 0; /* Allow ensuring internal scroll if needed, but we aim for fit */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.clinic-box {
|
|
|
|
@@ -808,7 +753,6 @@ watch(anjunganClientId, (newClientId, oldClientId) => {
|
|
|
|
|
border-radius: 16px;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
box-shadow: 0 4px 12px rgba(33, 150, 243, 0.12);
|
|
|
|
|
min-height: 240px;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
}
|
|
|
|
@@ -822,7 +766,7 @@ watch(anjunganClientId, (newClientId, oldClientId) => {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.clinic-title {
|
|
|
|
|
font-size: 22px;
|
|
|
|
|
font-size: clamp(14px, 1.15vw, 22px);
|
|
|
|
|
font-weight: 800;
|
|
|
|
|
color: var(--color-neutral-100);
|
|
|
|
|
letter-spacing: 0.5px;
|
|
|
|
@@ -860,13 +804,13 @@ watch(anjunganClientId, (newClientId, oldClientId) => {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tipe-label {
|
|
|
|
|
font-size: 20px;
|
|
|
|
|
font-size: clamp(14px, 1.04vw, 20px);
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
color: var(--color-primary-700);
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
letter-spacing: 1px;
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
padding: 8px 12px;
|
|
|
|
|
padding: clamp(4px, 0.42vw, 8px) clamp(8px, 0.63vw, 12px);
|
|
|
|
|
background: var(--color-primary-200);
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
display: inline-block;
|
|
|
|
@@ -899,7 +843,7 @@ watch(anjunganClientId, (newClientId, oldClientId) => {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.current-number {
|
|
|
|
|
font-size: 80px;
|
|
|
|
|
font-size: clamp(48px, 4.17vw, 80px);
|
|
|
|
|
font-weight: 900;
|
|
|
|
|
color: var(--color-neutral-900);
|
|
|
|
|
letter-spacing: 4px;
|
|
|
|
@@ -907,42 +851,6 @@ watch(anjunganClientId, (newClientId, oldClientId) => {
|
|
|
|
|
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.next-section {
|
|
|
|
|
text-align: center;
|
|
|
|
|
margin-top: 8px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.next-label {
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
color: var(--color-neutral-700);
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
letter-spacing: 0.5px;
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.next-numbers {
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.next-item {
|
|
|
|
|
background: var(--color-primary-600);
|
|
|
|
|
color: var(--color-neutral-100);
|
|
|
|
|
padding: 8px 16px;
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
font-size: 22px;
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
box-shadow: 0 2px 8px rgba(33, 150, 243, 0.25);
|
|
|
|
|
letter-spacing: 1px;
|
|
|
|
|
min-width: 60px;
|
|
|
|
|
text-align: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Empty state removed - only show in individual columns
|
|
|
|
|
|
|
|
|
|
/* ========== FOOTER STATS ========== */
|
|
|
|
|
.footer-stats-bar {
|
|
|
|
|
background: var(--color-neutral-100);
|
|
|
|
@@ -971,10 +879,7 @@ watch(anjunganClientId, (newClientId, oldClientId) => {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-icon-total,
|
|
|
|
|
.stat-icon-waiting {
|
|
|
|
|
background: var(--color-primary-200);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-icon-waiting,
|
|
|
|
|
.stat-icon-active {
|
|
|
|
|
background: var(--color-primary-200);
|
|
|
|
|
}
|
|
|
|
@@ -1016,120 +921,7 @@ watch(anjunganClientId, (newClientId, oldClientId) => {
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.ws-status-indicator {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 6px;
|
|
|
|
|
padding: 6px 12px;
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
margin-left: 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.ws-status-indicator.ws-connected {
|
|
|
|
|
background: rgba(76, 175, 80, 0.1);
|
|
|
|
|
color: #4caf50;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.ws-status-indicator.ws-disconnected {
|
|
|
|
|
background: rgba(244, 67, 54, 0.1);
|
|
|
|
|
color: #f44336;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.ws-status-text {
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
letter-spacing: 0.5px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ========== RESPONSIVE ========== */
|
|
|
|
|
// Base responsive rules untuk komponen individual
|
|
|
|
|
@media (max-width: 1919px) or (max-height: 1079px) {
|
|
|
|
|
// Adjust font sizes and spacing secara proporsional
|
|
|
|
|
.display-header {
|
|
|
|
|
padding: clamp(12px, 1.25vw, 24px) clamp(20px, 2.08vw, 40px);
|
|
|
|
|
margin-bottom: clamp(12px, 1.85vw, 20px);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.logo-circle {
|
|
|
|
|
width: clamp(48px, 4.17vw, 80px);
|
|
|
|
|
height: clamp(48px, 4.17vw, 80px);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.hospital-name {
|
|
|
|
|
font-size: clamp(28px, 2.5vw, 48px);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.display-subtitle {
|
|
|
|
|
font-size: clamp(14px, 1.04vw, 20px);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.time-large {
|
|
|
|
|
font-size: clamp(36px, 2.92vw, 56px);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.date-small {
|
|
|
|
|
font-size: clamp(12px, 0.94vw, 18px);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.hero-call-section {
|
|
|
|
|
padding: clamp(16px, 1.67vw, 32px);
|
|
|
|
|
margin-bottom: clamp(12px, 1.85vw, 20px);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.call-label {
|
|
|
|
|
font-size: clamp(20px, 1.46vw, 28px);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.call-number {
|
|
|
|
|
font-size: clamp(80px, 7.29vw, 140px);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.call-clinic {
|
|
|
|
|
font-size: clamp(24px, 1.88vw, 36px);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.clinics-grid {
|
|
|
|
|
gap: clamp(8px, 0.83vw, 16px);
|
|
|
|
|
margin-bottom: clamp(12px, 1.85vw, 20px);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.clinic-header-bar {
|
|
|
|
|
padding: clamp(10px, 1.04vw, 16px) clamp(12px, 1.04vw, 20px);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.clinic-title {
|
|
|
|
|
font-size: clamp(16px, 1.15vw, 22px);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.clinic-content {
|
|
|
|
|
padding: clamp(12px, 1.04vw, 20px);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.current-number {
|
|
|
|
|
font-size: clamp(56px, 4.17vw, 80px);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tipe-label {
|
|
|
|
|
font-size: clamp(16px, 1.04vw, 20px);
|
|
|
|
|
padding: clamp(6px, 0.42vw, 8px) clamp(10px, 0.63vw, 12px);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.footer-stats-bar {
|
|
|
|
|
padding: clamp(12px, 1.04vw, 20px) clamp(24px, 2.08vw, 40px);
|
|
|
|
|
gap: clamp(16px, 1.67vw, 32px);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-value {
|
|
|
|
|
font-size: clamp(28px, 2.08vw, 40px);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-label {
|
|
|
|
|
font-size: clamp(11px, 0.73vw, 14px);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@media (max-width: 1366px) {
|
|
|
|
|
.antrian-display-container {
|
|
|
|
|
padding: 16px;
|
|
|
|
@@ -1144,104 +936,24 @@ watch(anjunganClientId, (newClientId, oldClientId) => {
|
|
|
|
|
height: 64px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.logo-circle .v-icon {
|
|
|
|
|
font-size: 40px !important;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.hospital-name {
|
|
|
|
|
font-size: 36px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.display-subtitle {
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.time-large {
|
|
|
|
|
font-size: 48px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.date-small {
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.hero-call-section {
|
|
|
|
|
padding: 24px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.call-label {
|
|
|
|
|
font-size: 24px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.call-number {
|
|
|
|
|
font-size: 100px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.call-clinic {
|
|
|
|
|
font-size: 28px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.clinics-grid {
|
|
|
|
|
gap: 12px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.clinic-box {
|
|
|
|
|
min-height: 220px;
|
|
|
|
|
padding: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.clinic-header-bar {
|
|
|
|
|
padding: 14px 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.clinic-title {
|
|
|
|
|
font-size: 20px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.clinic-content {
|
|
|
|
|
padding: 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.current-number {
|
|
|
|
|
font-size: 72px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tipe-label {
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
padding: 6px 10px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.next-item {
|
|
|
|
|
font-size: 20px;
|
|
|
|
|
padding: 6px 14px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.footer-stats-bar {
|
|
|
|
|
padding: 16px 32px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-icon {
|
|
|
|
|
width: 56px;
|
|
|
|
|
height: 56px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-icon .v-icon {
|
|
|
|
|
font-size: 28px !important;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-value {
|
|
|
|
|
font-size: 36px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-label {
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.stat-divider {
|
|
|
|
|
height: 50px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.footer-message {
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
font-size: 60px;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -1251,57 +963,31 @@ watch(anjunganClientId, (newClientId, oldClientId) => {
|
|
|
|
|
gap: 16px;
|
|
|
|
|
text-align: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.header-left {
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 12px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.datetime-display {
|
|
|
|
|
text-align: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.clinics-grid {
|
|
|
|
|
grid-template-columns: repeat(2, 1fr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.footer-stats-bar {
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.stat-divider {
|
|
|
|
|
display: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.footer-message {
|
|
|
|
|
text-align: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@media (max-width: 768px) {
|
|
|
|
|
.clinics-grid {
|
|
|
|
|
grid-template-columns: 1fr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.hospital-name {
|
|
|
|
|
font-size: 28px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.time-large {
|
|
|
|
|
font-size: 36px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.call-number {
|
|
|
|
|
font-size: 80px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.current-number {
|
|
|
|
|
font-size: 60px;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Animations */
|
|
|
|
|
@keyframes pulse-highlight {
|
|
|
|
|
0%, 100% {
|
|
|
|
@@ -1327,5 +1013,67 @@ watch(anjunganClientId, (newClientId, oldClientId) => {
|
|
|
|
|
-webkit-font-smoothing: antialiased;
|
|
|
|
|
-moz-osx-font-smoothing: grayscale;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Single View Optimizations */
|
|
|
|
|
.clinic-box.is-single-view {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.clinic-box.is-single-view .clinic-header-bar {
|
|
|
|
|
padding: 32px 40px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.clinic-box.is-single-view .clinic-title {
|
|
|
|
|
font-size: 56px; /* Massive title */
|
|
|
|
|
text-align: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.clinic-box.is-single-view .clinic-count {
|
|
|
|
|
padding: 8px 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.clinic-box.is-single-view .count-text {
|
|
|
|
|
font-size: 24px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.clinic-box.is-single-view .clinic-content {
|
|
|
|
|
padding: 40px;
|
|
|
|
|
justify-content: space-evenly; /* Distribute vertical space */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.clinic-box.is-single-view .tipe-layanan-section {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
align-items: center;
|
|
|
|
|
flex: 1; /* Take up available space */
|
|
|
|
|
justify-content: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.clinic-box.is-single-view .tipe-label {
|
|
|
|
|
font-size: 32px; /* Much larger label */
|
|
|
|
|
padding: 12px 24px;
|
|
|
|
|
margin-bottom: 24px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.clinic-box.is-single-view .current-label {
|
|
|
|
|
font-size: 24px;
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.clinic-box.is-single-view .current-number {
|
|
|
|
|
font-size: 180px; /* Enormous number */
|
|
|
|
|
line-height: 1;
|
|
|
|
|
margin: 20px 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.clinic-box.is-single-view .empty-text-small {
|
|
|
|
|
font-size: 24px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.clinic-box.is-single-view .v-icon {
|
|
|
|
|
transform: scale(1.5); /* Scale up icons */
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
|