Update tampilan pasien aktif

This commit is contained in:
2026-07-24 14:56:15 +07:00
parent fdd7063d80
commit 8b730f7226
+509 -137
View File
@@ -10,100 +10,336 @@
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
<style> <style>
body { :root{
background: linear-gradient(135deg,#eef2f7,#f8fafc); --primary: #2563eb;
font-family: 'Segoe UI', sans-serif; --primary-soft: #dbeafe;
--success: #16a34a;
--success-soft: #dcfce7;
--warning: #d97706;
--warning-soft: #fef3c7;
--danger: #dc2626;
--danger-soft: #fee2e2;
--secondary: #64748b;
--secondary-soft: #f1f5f9;
--surface: rgba(255,255,255,.92);
--shadow: 0 10px 35px rgba(15,23,42,.08);
--radius: 24px;
} }
.header-title { body{
font-weight: bold; background:
letter-spacing: .5px; radial-gradient(circle at top right, #dbeafe 0%, transparent 35%),
radial-gradient(circle at bottom left, #dcfce7 0%, transparent 35%),
linear-gradient(180deg, #f8fafc 0%, #eef4ff 100%);
font-family: 'Segoe UI', sans-serif;
color: #0f172a;
min-height: 100vh;
} }
.card { .header-title{
border-radius: 18px; font-size: 2rem;
border: none; font-weight: 800;
transition: all .2s ease; letter-spacing: -.5px;
color: #0f172a;
} }
.card:hover { /* CARD */
transform: translateY(-3px); .card{
box-shadow: 0 10px 25px rgba(0,0,0,0.1); border: none;
border-radius: var(--radius);
background: var(--surface);
backdrop-filter: blur(18px);
box-shadow: var(--shadow);
transition: all .3s ease;
overflow: hidden;
} }
.fase-box { .card:hover{
padding: 10px 12px; transform: translateY(-5px);
border-radius: 12px; box-shadow: 0 18px 45px rgba(15,23,42,.14);
margin-bottom: 6px;
font-size: 13px;
} }
.done { /* MENU */
background: #d1e7dd; .menu-row{
border-left: 5px solid #198754; display:flex;
gap:12px;
margin-bottom:18px;
} }
.active { .menu-row .menu-card{
background: #fff3cd; flex:1;
border-left: 5px solid #ffc107;
} }
.locked { /* MENU CARD - CLEAN VERSION */
background: #e9ecef; .menu-card{
color: #999; background: #ffffff;
border: 1px solid #e2e8f0;
border-radius: 16px;
padding: 14px 10px;
text-align: center;
cursor: pointer;
transition: all .2s ease;
box-shadow: 0 4px 12px rgba(0,0,0,.04);
color: #0f172a;
} }
.badge-emergency { /* hover */
background:#dc3545; .menu-card:hover{
color:white; transform: translateY(-2px);
animation:blinkRed 1s infinite; border-color: #cbd5e1;
font-weight:bold; box-shadow: 0 8px 18px rgba(0,0,0,.06);
} }
.badge-non { /* ICON */
background:#ffc107; .menu-icon{
color:black; font-size: 1.6rem;
font-weight:500; margin-bottom: 6px;
opacity: .9;
} }
.badge-pending { /* TEXT */
background:#198754; .menu-card h6{
color:white; font-size: .85rem;
font-weight: 600;
margin: 0;
color: #334155;
} }
@keyframes blinkRed{ /* BACK BUTTON STYLE */
0%,100%{opacity:1;} .menu-card.back{
50%{opacity:.4;} background: #f1f5f9;
} }
.btn-sm { /* SUMMARY */
font-size: 12px; .summary-card{
border-radius: 20px; background: rgba(255,255,255,.95);
padding: 4px 10px; backdrop-filter: blur(18px);
border-radius: 28px;
padding: 28px;
box-shadow: var(--shadow);
margin-bottom: 30px;
border: 1px solid rgba(255,255,255,.7);
} }
.menu-card { .summary-header{
background: white; display: flex;
border-radius: 15px; justify-content: space-between;
padding: 15px; align-items: center;
text-align: center; margin-bottom: 28px;
cursor: pointer;
margin-bottom: 15px;
transition: .2s;
} }
.menu-card:hover { .summary-header h5{
background: #0d6efd; font-size: 1.6rem;
color: white; font-weight: 800;
margin: 0;
color: #0f172a;
} }
.menu-icon { .summary-header small{
font-size: 24px; color: #64748b;
font-size: .95rem;
} }
.empty-state { .summary-stats{
margin-top: 80px; display: grid;
opacity: .6; grid-template-columns: repeat(auto-fit,minmax(150px,1fr));
gap: 18px;
}
.stat-item{
border-radius: 22px;
padding: 24px 18px;
text-align: center;
transition: all .3s ease;
}
.stat-item:hover{
transform: translateY(-5px);
}
.stat-number{
font-size: 2.2rem;
font-weight: 800;
line-height: 1;
margin-bottom: 8px;
}
.stat-label{
font-size: .95rem;
font-weight: 600;
opacity: .9;
}
.stat-item.emergency{
background: var(--danger-soft);
color: var(--danger);
}
.stat-item.non{
background: var(--warning-soft);
color: var(--warning);
}
.stat-item.pending{
background: var(--success-soft);
color: var(--success);
}
/* ACCORDION */
.accordion-item{
border: none !important;
background: transparent;
}
.accordion-button{
background: transparent;
border: none;
padding: 1.4rem;
box-shadow: none !important;
}
.accordion-button:not(.collapsed){
background: #f8fafc;
color: inherit;
}
.accordion-button::after{
transition: .3s;
}
/* PHASE */
.fase-box{
padding: 14px 18px;
border-radius: 18px;
margin-bottom: 12px;
font-size: .95rem;
transition: .25s;
}
.fase-box:hover{
transform: translateX(4px);
}
.done{
background: var(--success-soft);
border-left: 6px solid var(--success);
}
.active{
background: var(--warning-soft);
border-left: 6px solid var(--warning);
}
.locked{
background: var(--secondary-soft);
border-left: 6px solid #cbd5e1;
color: #94a3b8;
}
/* BADGES */
.badge{
padding: .65rem 1rem;
border-radius: 999px;
font-size: .8rem;
font-weight: 700;
letter-spacing: .3px;
}
.badge-emergency{
background: linear-gradient(135deg,#ef4444,#dc2626);
color: white;
box-shadow: 0 6px 18px rgba(220,38,38,.30);
}
.badge-non{
background: linear-gradient(135deg,#facc15,#eab308);
color: #713f12;
}
.badge-pending{
background: linear-gradient(135deg,#22c55e,#16a34a);
color: white;
}
/* BUTTON */
.btn-sm{
border-radius: 999px;
padding: .45rem 1rem;
font-size: .82rem;
font-weight: 600;
box-shadow: 0 4px 12px rgba(0,0,0,.12);
}
/* EMPTY */
.empty-state{
margin-top: 100px;
padding: 50px 20px;
text-align: center;
opacity: .75;
}
.empty-state h1{
font-size: 5rem;
margin-bottom: 15px;
}
/* SCROLLBAR */
::-webkit-scrollbar{
width: 10px;
}
::-webkit-scrollbar-thumb{
background: #cbd5e1;
border-radius: 10px;
}
::-webkit-scrollbar-thumb:hover{
background: #94a3b8;
}
.top-bar{
display:flex;
justify-content:space-between;
align-items:center;
padding:14px 18px;
background:rgba(255,255,255,.8);
border-radius:18px;
box-shadow:0 6px 18px rgba(0,0,0,.06);
backdrop-filter: blur(10px);
}
.top-bar .title{
font-weight:800;
font-size:1.1rem;
color:#0f172a;
}
.time-box{
text-align:right;
font-weight:600;
color:#334155;
}
#clock{
font-size:1rem;
font-weight:700;
}
#date{
font-size:.8rem;
color:#64748b;
}
#autoPanel.minimized{
opacity: .55;
transform: scale(.98);
filter: blur(.2px);
transition: all .4s ease;
}
/* supaya masih enak dibaca */
#autoPanel.minimized .card{
box-shadow: 0 2px 8px rgba(0,0,0,.05);
} }
</style> </style>
</head> </head>
@@ -112,99 +348,235 @@ body {
<div class="container py-4"> <div class="container py-4">
<div class="d-flex justify-content-between align-items-center mb-3"> <?php
<h4 class="header-title">🚑 Pasien Aktif</h4> $totalPasien = count($pasien);
<small class="text-muted">Auto refresh 5 detik</small> $totalEmergency = 0;
</div> $totalNon = 0;
$totalPending = 0;
<div class="menu-card" onclick="window.location.href='/pasien/input'"> foreach ($pasien as $p) {
<div class="menu-icon"></div> switch ($p['order_type'] ?? '') {
<h6 class="mt-2 mb-0">Tambah Pasien Baru</h6> case 'True Emergency':
</div> $totalEmergency++;
break;
case 'Non Emergency':
$totalNon++;
break;
default:
$totalPending++;
break;
}
}
?>
<!-- ================= LIST PASIEN ================= --> <!-- ================= SUMMARY ================= -->
<div class="summary-card mb-4">
<div class="summary-header">
<div>
<h5 class="mb-1">🚑 Pasien Aktif</h5>
<small>Monitoring Pasien Ambulans 119 Saat Ini Berjumlah <?= $totalPasien ?> Pasien</small>
</div>
<div class="time-box">
<div id="clock">--:--:--</div>
<div id="date">-</div>
</div>
</div>
<div class="summary-stats">
<div class="stat-item emergency">
<div class="stat-number"><?= $totalEmergency ?></div>
<div class="stat-label">True Emergency</div>
</div>
<div class="stat-item non">
<div class="stat-number"><?= $totalNon ?></div>
<div class="stat-label">Non Emergency</div>
</div>
<div class="stat-item pending">
<div class="stat-number"><?= $totalPending ?></div>
<div class="stat-label">Pending Order</div>
</div>
</div>
</div>
<div class="menu-row">
<div class="menu-card" onclick="window.location.href='../pasien/input'">
<div class="menu-icon"></div>
<h6 class="mt-2 mb-0">Tambah Pasien Baru</h6>
</div>
<div class="menu-card back" onclick="window.location.href='/silaras119/public/dashboard'">
<div class="menu-icon">🏠</div>
<h6 class="mt-2 mb-0">Kembali ke Dashboard Utama Silaras 119</h6>
</div>
</div>
<div id="autoPanel">
<?php if (!empty($pasien)): ?> <?php if (!empty($pasien)): ?>
<?php foreach($pasien as $p): ?> <div class="accordion" id="accordionPasien">
<?php <?php foreach($pasien as $index => $p): ?>
$fase = $p['fase'] ?? 1;
$orderType = $p['order_type'] ?? 'Pending Order';
switch ($orderType) { <?php
case 'True Emergency': $fase = $p['fase'] ?? 1;
$badgeClass = 'badge-emergency'; $orderType = $p['order_type'] ?? 'Pending Order';
break;
case 'Non Emergency':
$badgeClass = 'badge-non';
break;
default:
$badgeClass = 'badge-pending';
break;
}
$steps = [ switch ($orderType) {
1 => "Pre Dispatch", case 'True Emergency':
2 => "Dispatch", $badgeClass = 'badge-emergency';
3 => "En Route", break;
4 => "On Scene", case 'Non Emergency':
5 => "Transport", $badgeClass = 'badge-non';
6 => "Selesai" break;
]; default:
?> $badgeClass = 'badge-pending';
break;
}
<div class="card mb-3 shadow-sm"> $steps = [
<div class="card-body"> 1 => "Pre Dispatch",
2 => "Dispatch",
3 => "En Route",
4 => "On Scene",
5 => "Transport",
6 => "Selesai"
];
?>
<div class="card mb-3 shadow-sm">
<div class="accordion-item border-0 rounded-4">
<h2 class="accordion-header">
<button class="accordion-button collapsed rounded-4"
type="button"
data-bs-toggle="collapse"
data-bs-target="#collapse<?= $p['id'] ?>">
<div class="w-100">
<div class="d-flex justify-content-between align-items-center">
<h6 class="mb-1">👤 <?= esc($p['nama']) ?></h6>
<span class="badge <?= $badgeClass ?>">
<?= esc($orderType) ?>
</span>
</div>
<small class="text-muted">
<?= esc($p['alamat'] ?? '-') ?>
</small>
</div>
</button>
</h2>
<div id="collapse<?= $p['id'] ?>"
class="accordion-collapse collapse"
data-bs-parent="#accordionPasien">
<div class="accordion-body">
<?php foreach($steps as $key => $label): ?>
<?php
$class =
$fase > $key ? 'done' :
($fase == $key ? 'active' : 'locked');
?>
<div class="fase-box <?= $class ?>">
<div class="d-flex justify-content-between align-items-center">
<?php if($key == 1): ?>
<a href="../pasien/predispatch/<?= $p['id'] ?>"
class="text-decoration-none text-dark">
<b><?= $label ?></b>
</a>
<?php else: ?>
<b><?= $label ?></b>
<?php endif; ?>
<?php if($fase == $key && $fase < 6): ?>
<a href="../pasien/next/<?= $p['id'] ?>"
class="btn btn-sm btn-warning">
➡️ Lanjut
</a>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
<!-- HEADER -->
<div class="d-flex justify-content-between align-items-center">
<h6 class="mb-1">👤 <?= esc($p['nama']) ?></h6>
<span class="badge <?= $badgeClass ?>">
<?= esc($orderType) ?>
</span>
</div> </div>
<p class="text-muted small mb-2">
<?= esc($p['alamat'] ?? '-') ?>
</p>
<hr>
<!-- FASE AMBULANS-->
<?php foreach($steps as $key => $label): ?>
<?php
$class =
$fase > $key ? 'done' :
($fase == $key ? 'active' : 'locked');
?>
<div class="fase-box <?= $class ?>">
<div class="d-flex justify-content-between align-items-center">
<?php if($key == 1): ?>
<!-- PRE DISPATCH -->
<a href="/pasien/predispatch/<?= $p['id'] ?>" style="text-decoration:none; color:inherit;">
<b><?= $label ?></b>
</a>
<?php else: ?>
<b><?= $label ?></b>
<?php endif; ?>
<?php if($fase == $key && $fase < 6): ?>
<a href="/pasien/next/<?= $p['id'] ?>" class="btn btn-sm btn-warning">
➡️ Lanjut
</a>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
</div> </div>
</div> </div>
<?php endforeach; ?> </div>
<?php endforeach; ?>
</div>
<?php else: ?> <?php else: ?>
<div class="text-center empty-state"> <div class="text-center empty-state">
<h1>🚑</h1> <h1>🚑</h1>
<p>Tidak ada pasien aktif</p> <p>Tidak ada pasien aktif</p>
</div> </div>
<?php endif; ?> <?php endif; ?>
</div> </div>
</div>
<script>
function updateTime(){
const now = new Date();
const time = now.toLocaleTimeString('id-ID', {
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
const date = now.toLocaleDateString('id-ID', {
weekday: 'long',
day: '2-digit',
month: 'long',
year: 'numeric'
});
document.getElementById('clock').innerText = time;
document.getElementById('date').innerText = date;
}
setInterval(updateTime, 1000);
updateTime();
let idleTime = 0;
let panel = document.getElementById("autoPanel");
// reset kalau user aktif
function resetIdle(){
idleTime = 0;
panel.classList.remove("minimized");
}
// event user aktif
document.addEventListener("mousemove", resetIdle);
document.addEventListener("click", resetIdle);
document.addEventListener("keydown", resetIdle);
document.addEventListener("scroll", resetIdle);
// timer idle
setInterval(() => {
idleTime++;
// 10 detik tidak aktif → minimize
if(idleTime >= 10){
panel.classList.add("minimized");
}
}, 1000);
</script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
</body> </body>
</html> </html>