4463 lines
114 KiB
HTML
4463 lines
114 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="id">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<title>SiLARAS 119 – Sistem Laporan Aktivitas Respon Ambulans 119</title>
|
||
<link rel="icon" type="image/x-icon"
|
||
href="https://raw.githubusercontent.com/ambulan119/inventarisambulans119/refs/heads/main/silarass2.ico">
|
||
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
|
||
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
|
||
|
||
<style>
|
||
|
||
/* ======================================================
|
||
ROOT – WARNA & SISTEM DESAIN (REFINED)
|
||
====================================================== */
|
||
:root{
|
||
--primary:#0d6efd;
|
||
--primary-soft:#60a5fa;
|
||
--accent:#22d3ee;
|
||
--danger:#ef4444;
|
||
--dark:#020617;
|
||
--glass:rgba(255,255,255,.95);
|
||
--glass-dark:rgba(15,23,42,.55);
|
||
--border:#cbd5e1;
|
||
--text:#0f172a;
|
||
--muted:#64748b;
|
||
--radius-xl:1.4rem;
|
||
--radius-lg:1rem;
|
||
--radius-pill:999px;
|
||
}
|
||
|
||
/* ======================================================
|
||
BODY – BACKGROUND COMMAND CENTER
|
||
====================================================== */
|
||
body{
|
||
min-height:100vh;
|
||
background:
|
||
radial-gradient(circle at top right, rgba(34,211,238,.15), transparent 40%),
|
||
linear-gradient(rgba(2,6,23,.82), rgba(2,6,23,.82)),
|
||
url("https://raw.githubusercontent.com/ambulan119/inventarisambulans119/refs/heads/main/silarasss.jpg")
|
||
center/cover no-repeat fixed;
|
||
font-family:"Inter","Segoe UI",system-ui,sans-serif;
|
||
color:var(--text);
|
||
overflow-x:hidden;
|
||
}
|
||
|
||
/* ======================================================
|
||
NAVBAR – GLASS ELITE
|
||
====================================================== */
|
||
.navbar{
|
||
backdrop-filter: blur(20px) saturate(180%);
|
||
background:linear-gradient(
|
||
135deg,
|
||
rgba(255,255,255,.12),
|
||
rgba(255,255,255,.02)
|
||
) !important;
|
||
border-bottom:1px solid rgba(255,215,0,.8);
|
||
box-shadow:0 10px 30px rgba(0,0,0,.35);
|
||
}
|
||
|
||
/* LOGO */
|
||
.navbar img{
|
||
border-radius:14px;
|
||
padding:6px;
|
||
background:rgba(255,255,255,.25);
|
||
box-shadow:0 0 25px rgba(34,211,238,.45);
|
||
animation: logoPulse 3s infinite;
|
||
}
|
||
|
||
/* ======================================================
|
||
HERO TITLE – EMERGENCY COMMAND
|
||
====================================================== */
|
||
.laras-title{
|
||
font-size:3.8rem;
|
||
font-weight:900;
|
||
letter-spacing:1.6px;
|
||
color:#fff;
|
||
text-shadow:
|
||
0 0 20px rgba(34,211,238,.25),
|
||
0 0 40px rgba(13,110,253,.35);
|
||
animation:titleSlide .9s ease both;
|
||
position:relative;
|
||
}
|
||
|
||
.laras-title::after{
|
||
content:"";
|
||
position:absolute;
|
||
inset:0;
|
||
background:linear-gradient(
|
||
120deg,
|
||
transparent,
|
||
rgba(255,255,255,.85),
|
||
transparent
|
||
);
|
||
transform:translateX(-120%);
|
||
animation:shine 4s infinite;
|
||
}
|
||
|
||
.laras-subtitle{
|
||
font-size:2.4rem;
|
||
font-weight:600;
|
||
color:rgba(255,255,255,.92);
|
||
text-shadow:0 0 12px rgba(0,0,0,.6);
|
||
animation:fadeIn .8s ease .35s both;
|
||
}
|
||
|
||
.laras-tagline{
|
||
font-size:1.6rem;
|
||
font-style:italic;
|
||
color:rgba(255,255,255,.85);
|
||
animation:fadeIn .8s ease .55s both;
|
||
}
|
||
|
||
.text-119{
|
||
color:#ff2d2d;
|
||
text-shadow:
|
||
0 0 8px rgba(255,45,45,.7),
|
||
0 0 18px rgba(255,45,45,.5);
|
||
}
|
||
|
||
.laras-year{
|
||
font-size:1.9rem;
|
||
letter-spacing:.9em;
|
||
color:rgba(255,230,0,.95);
|
||
text-shadow:0 0 12px rgba(255,215,0,.6);
|
||
animation:fadeIn .9s ease .9s both;
|
||
}
|
||
|
||
/* ======================================================
|
||
CARD / PANEL – GLASS PREMIUM
|
||
====================================================== */
|
||
.card{
|
||
background:linear-gradient(
|
||
180deg,
|
||
rgba(255,255,255,.98),
|
||
rgba(248,250,252,.95)
|
||
);
|
||
border-radius:var(--radius-xl);
|
||
border:1px solid rgba(255,255,255,.6);
|
||
box-shadow:
|
||
0 15px 35px rgba(0,0,0,.22),
|
||
inset 0 0 0 1px rgba(255,255,255,.6);
|
||
animation:panelUp .6s ease both;
|
||
transition:.35s ease;
|
||
}
|
||
|
||
.card:hover{
|
||
transform:translateY(-6px) scale(1.01);
|
||
box-shadow:
|
||
0 25px 55px rgba(0,0,0,.35),
|
||
0 0 0 1px rgba(34,211,238,.4);
|
||
}
|
||
|
||
.card-header{
|
||
background:linear-gradient(135deg,#e0f2fe,#f8fafc);
|
||
font-weight:800;
|
||
letter-spacing:.3px;
|
||
border-bottom:1px solid var(--border);
|
||
}
|
||
|
||
/* ======================================================
|
||
FORM – CLEAN & MODERN
|
||
====================================================== */
|
||
.form-label{
|
||
font-weight:600;
|
||
color:#020617;
|
||
}
|
||
|
||
.form-control,
|
||
.form-select{
|
||
border-radius:var(--radius-lg);
|
||
border:1px solid var(--border);
|
||
padding:.55rem .75rem;
|
||
transition:.25s ease;
|
||
}
|
||
|
||
.form-control::placeholder{
|
||
color:#94a3b8;
|
||
}
|
||
|
||
.form-control:focus,
|
||
.form-select:focus{
|
||
border-color:var(--primary);
|
||
box-shadow:
|
||
0 0 0 .2rem rgba(13,110,253,.25),
|
||
0 0 20px rgba(34,211,238,.35);
|
||
}
|
||
|
||
/* ======================================================
|
||
BUTTON – DIGITAL GLOW
|
||
====================================================== */
|
||
.btn{
|
||
border-radius:var(--radius-pill);
|
||
font-weight:700;
|
||
letter-spacing:.3px;
|
||
transition:.3s ease;
|
||
}
|
||
|
||
.btn-primary{
|
||
background:linear-gradient(135deg,var(--primary),var(--accent));
|
||
border:0;
|
||
box-shadow:0 6px 18px rgba(13,110,253,.4);
|
||
}
|
||
|
||
.btn-primary:hover{
|
||
transform:translateY(-2px);
|
||
box-shadow:
|
||
0 12px 30px rgba(13,110,253,.6),
|
||
0 0 20px rgba(34,211,238,.55);
|
||
}
|
||
|
||
/* ======================================================
|
||
SHIFT CARD – ACTIVE INDICATOR
|
||
====================================================== */
|
||
.card-shift{
|
||
cursor:pointer;
|
||
position:relative;
|
||
overflow:hidden;
|
||
}
|
||
|
||
.card-shift::before{
|
||
content:"";
|
||
position:absolute;
|
||
inset:0;
|
||
background:linear-gradient(
|
||
120deg,
|
||
transparent,
|
||
rgba(34,211,238,.25),
|
||
transparent
|
||
);
|
||
opacity:0;
|
||
transition:.4s ease;
|
||
}
|
||
|
||
.card-shift:hover::before{
|
||
opacity:1;
|
||
}
|
||
|
||
.card-shift.shift-active{
|
||
border:2px solid var(--primary);
|
||
box-shadow:
|
||
0 0 0 10px rgba(13,110,253,.25),
|
||
0 0 25px rgba(34,211,238,.6);
|
||
}
|
||
|
||
/* ======================================================
|
||
PRINT / PDF MODE – SUPER RAPAT
|
||
====================================================== */
|
||
@media print{
|
||
body{
|
||
font-size:6.5px!important;
|
||
background:#fff!important;
|
||
}
|
||
|
||
*{
|
||
animation:none!important;
|
||
transition:none!important;
|
||
box-shadow:none!important;
|
||
}
|
||
|
||
.no-print{display:none!important;}
|
||
}
|
||
|
||
/* ======================================================
|
||
ANIMATIONS
|
||
====================================================== */
|
||
@keyframes fadeIn{
|
||
from{opacity:0;transform:translateY(8px)}
|
||
to{opacity:1}
|
||
}
|
||
@keyframes panelUp{
|
||
from{opacity:0;transform:translateY(14px)}
|
||
to{opacity:1}
|
||
}
|
||
@keyframes titleSlide{
|
||
from{opacity:0;transform:translateX(-24px)}
|
||
to{opacity:1}
|
||
}
|
||
@keyframes shine{
|
||
to{transform:translateX(130%)}
|
||
}
|
||
@keyframes logoPulse{
|
||
0%,100%{transform:scale(1)}
|
||
50%{transform:scale(1.12)}
|
||
}
|
||
|
||
/* ======================================================
|
||
TABLE FINAL – CLEAN & PROFESSIONAL
|
||
====================================================== */
|
||
|
||
.table{
|
||
width:100%;
|
||
background:#ffffff;
|
||
border:1px solid #cbd5e1;
|
||
border-radius:0.75rem;
|
||
border-collapse:collapse;
|
||
font-size:0.85rem;
|
||
}
|
||
|
||
/* HEADER */
|
||
.table thead th{
|
||
background:#0d6efd;
|
||
color:#ffffff;
|
||
font-weight:700;
|
||
text-align:center;
|
||
vertical-align:middle;
|
||
padding:10px 8px;
|
||
border:1px solid #1e40af;
|
||
white-space:nowrap;
|
||
}
|
||
|
||
/* BODY */
|
||
.table tbody td{
|
||
padding:8px 10px;
|
||
border:1px solid #e2e8f0;
|
||
vertical-align:top;
|
||
line-height:1.4;
|
||
}
|
||
|
||
/* STRIPED */
|
||
.table tbody tr:nth-child(even){
|
||
background:#f8fafc;
|
||
}
|
||
|
||
/* HOVER */
|
||
.table-hover tbody tr:hover{
|
||
background:#e0f2fe;
|
||
}
|
||
|
||
/* RESPONSIVE WRAP */
|
||
.table-responsive{
|
||
border-radius:0.75rem;
|
||
overflow:auto;
|
||
}
|
||
|
||
/* ======================================================
|
||
ATUR LEBAR KOLOM KHUSUS
|
||
====================================================== */
|
||
|
||
/* Status Kegawatan */
|
||
.table th:nth-child(3),
|
||
.table td:nth-child(3){
|
||
min-width:130px;
|
||
text-align:center;
|
||
}
|
||
|
||
/* Wilayah */
|
||
.table th:nth-child(4),
|
||
.table td:nth-child(4){
|
||
min-width:110px;
|
||
text-align:center;
|
||
}
|
||
|
||
/* Derajat Transfer */
|
||
.table th:nth-child(6),
|
||
.table td:nth-child(6){
|
||
min-width:110px;
|
||
text-align:center;
|
||
}
|
||
|
||
/* Status */
|
||
.table th:nth-child(7),
|
||
.table td:nth-child(7){
|
||
min-width:110px;
|
||
text-align:center;
|
||
}
|
||
|
||
.table{
|
||
table-layout:fixed;
|
||
}
|
||
/* ================= BBM ================= */
|
||
|
||
.bbm-container{
|
||
|
||
background:
|
||
linear-gradient(
|
||
to bottom,
|
||
#2b2b2b,
|
||
#111
|
||
);
|
||
|
||
padding:5px 8px;
|
||
|
||
border-radius:8px;
|
||
|
||
display:inline-flex;
|
||
align-items:center;
|
||
gap:3px;
|
||
|
||
border:1px solid #2f2f2f;
|
||
|
||
box-shadow:
|
||
inset 0 1px 2px rgba(255,255,255,.05),
|
||
0 1px 3px rgba(0,0,0,.2);
|
||
}
|
||
|
||
/* ================= BAR ================= */
|
||
|
||
.bbm-box{
|
||
|
||
width:14px;
|
||
height:10px;
|
||
|
||
border-radius:2px;
|
||
|
||
border:1px solid #444;
|
||
|
||
background:#111;
|
||
|
||
transition:.3s;
|
||
}
|
||
|
||
/* ================= OFF ================= */
|
||
|
||
.bbm-off{
|
||
background:#1a1a1a;
|
||
}
|
||
|
||
/* ================= ACTIVE ================= */
|
||
|
||
.bbm-green{
|
||
|
||
background:#00c853;
|
||
|
||
box-shadow:
|
||
0 0 6px #00ff88;
|
||
}
|
||
|
||
.bbm-yellow{
|
||
|
||
background:#ffd600;
|
||
|
||
box-shadow:
|
||
0 0 6px orange;
|
||
}
|
||
|
||
.bbm-red{
|
||
|
||
background:#d50000;
|
||
|
||
box-shadow:
|
||
0 0 6px red;
|
||
}
|
||
/* ================= LABEL ================= */
|
||
|
||
.bbm-label{
|
||
font-size:12px;
|
||
font-weight:900;
|
||
width:16px;
|
||
text-align:center;
|
||
display:inline-block;
|
||
}
|
||
|
||
/* E */
|
||
.bbm-e{
|
||
color:#ff2d2d !important;
|
||
}
|
||
|
||
/* F */
|
||
.bbm-f{
|
||
color:#00ff7b !important;
|
||
}
|
||
|
||
/* ===================================================
|
||
COMPACT REALISTIC OXYGEN CYLINDER
|
||
=================================================== */
|
||
|
||
/* ================= GROUP ================= */
|
||
.oxy-group{
|
||
display:flex;
|
||
gap:10px;
|
||
flex-wrap:wrap;
|
||
margin-top:6px;
|
||
}
|
||
|
||
/* ================= ITEM ================= */
|
||
.oxy-item{
|
||
width:52px;
|
||
text-align:center;
|
||
position:relative;
|
||
}
|
||
|
||
/* ===================================================
|
||
HANDLE TABUNG
|
||
=================================================== */
|
||
|
||
.oxy-item::before{
|
||
content:"";
|
||
position:absolute;
|
||
top:-5px;
|
||
left:50%;
|
||
transform:translateX(-50%);
|
||
width:16px;
|
||
height:5px;
|
||
border-radius:5px;
|
||
background:
|
||
linear-gradient(
|
||
to bottom,
|
||
#95a5a6,
|
||
#636e72
|
||
);
|
||
|
||
border:1px solid #495057;
|
||
}
|
||
|
||
/* ===================================================
|
||
KEPALA TABUNG
|
||
=================================================== */
|
||
|
||
.oxy-head{
|
||
|
||
width:13px;
|
||
height:10px;
|
||
margin:auto;
|
||
border-radius:4px 4px 2px 2px;
|
||
background:
|
||
linear-gradient(
|
||
to bottom,
|
||
#dfe6e9,
|
||
#636e72
|
||
);
|
||
|
||
border:1px solid #495057;
|
||
position:relative;
|
||
z-index:5;
|
||
}
|
||
|
||
/* NOZZLE */
|
||
.oxy-head::after{
|
||
content:"";
|
||
position:absolute;
|
||
top:-3px;
|
||
left:50%;
|
||
transform:translateX(-50%);
|
||
width:5px;
|
||
height:3px;
|
||
border-radius:2px;
|
||
background:#2d3436;
|
||
}
|
||
|
||
/* ===================================================
|
||
TABUNG
|
||
=================================================== */
|
||
|
||
.oxy-cylinder{
|
||
|
||
width:28px;
|
||
height:78px;
|
||
margin:auto;
|
||
position:relative;
|
||
overflow:hidden;
|
||
border-radius:18px;
|
||
border:2px solid #adb5bd;
|
||
background:
|
||
linear-gradient(
|
||
to right,
|
||
#bfc6cc 0%,
|
||
#ffffff 50%,
|
||
#adb5bd 100%
|
||
);
|
||
box-shadow:
|
||
inset 0 0 6px rgba(255,255,255,.8),
|
||
inset 0 -5px 8px rgba(0,0,0,.08),
|
||
0 2px 4px rgba(0,0,0,.08);
|
||
}
|
||
|
||
/* EFEK KILAP */
|
||
.oxy-cylinder::before{
|
||
content:"";
|
||
position:absolute;
|
||
top:8px;
|
||
bottom:8px;
|
||
left:50%;
|
||
width:3px;
|
||
transform:translateX(-50%);
|
||
border-radius:10px;
|
||
background:
|
||
linear-gradient(
|
||
to bottom,
|
||
rgba(255,255,255,.9),
|
||
rgba(255,255,255,.05)
|
||
);
|
||
z-index:3;
|
||
}
|
||
|
||
/* DUDUKAN */
|
||
.oxy-cylinder::after{
|
||
content:"";
|
||
position:absolute;
|
||
bottom:-4px;
|
||
left:50%;
|
||
transform:translateX(-50%);
|
||
width:16px;
|
||
height:5px;
|
||
border-radius:50%;
|
||
background:
|
||
linear-gradient(
|
||
to bottom,
|
||
#636e72,
|
||
#2d3436
|
||
);
|
||
}
|
||
|
||
/* ===================================================
|
||
ISI TABUNG
|
||
=================================================== */
|
||
|
||
.oxy-fill{
|
||
|
||
position:absolute;
|
||
|
||
bottom:0;
|
||
left:0;
|
||
width:100%;
|
||
transition:.35s ease;
|
||
border-radius:0 0 14px 14px;
|
||
z-index:2;
|
||
}
|
||
|
||
/* ===================================================
|
||
LABEL
|
||
=================================================== */
|
||
|
||
.oxy-label{
|
||
font-size:9px;
|
||
font-weight:700;
|
||
margin-top:4px;
|
||
color:#495057;
|
||
line-height:1.1;
|
||
}
|
||
|
||
/* ===================================================
|
||
STATUS
|
||
=================================================== */
|
||
|
||
.oxy-status{
|
||
font-size:9px;
|
||
font-weight:800;
|
||
margin-top:1px;
|
||
letter-spacing:.2px;
|
||
}
|
||
|
||
.oxy-green{
|
||
color:#198754;
|
||
}
|
||
.oxy-yellow{
|
||
color:#f39c12;
|
||
}
|
||
.oxy-red{
|
||
color:#dc3545;
|
||
}
|
||
.oxy-gray{
|
||
color:#6c757d;
|
||
}
|
||
|
||
/* ===================================================
|
||
BADGE CADANGAN
|
||
=================================================== */
|
||
|
||
.oxy-badge-tersedia{
|
||
background:
|
||
linear-gradient(
|
||
to right,
|
||
#198754,
|
||
#20c997
|
||
);
|
||
color:#fff;
|
||
padding:3px 7px;
|
||
border-radius:6px;
|
||
font-size:10px;
|
||
font-weight:700;
|
||
}
|
||
|
||
.oxy-badge-kosong{
|
||
background:
|
||
linear-gradient(
|
||
to right,
|
||
#c0392b,
|
||
#ff7675
|
||
);
|
||
color:#fff;
|
||
padding:3px 7px;
|
||
border-radius:6px;
|
||
font-size:10px;
|
||
font-weight:700;
|
||
}
|
||
|
||
/* ===================================================
|
||
KRITIS
|
||
=================================================== */
|
||
@keyframes oxyBlink{
|
||
0%{
|
||
opacity:1;
|
||
}
|
||
50%{
|
||
opacity:.4;
|
||
}
|
||
100%{
|
||
opacity:1;
|
||
}
|
||
}
|
||
.oxy-critical{
|
||
animation:oxyBlink .9s infinite;
|
||
}
|
||
|
||
/* ===================================================
|
||
MOBILE
|
||
=================================================== */
|
||
@media(max-width:768px){
|
||
.oxy-group{
|
||
gap:7px;
|
||
}
|
||
.oxy-item{
|
||
width:46px;
|
||
}
|
||
.oxy-cylinder{
|
||
width:24px;
|
||
height:68px;
|
||
}
|
||
}
|
||
|
||
.fuel-semi{
|
||
width: 220px;
|
||
margin: auto;
|
||
text-align: center;
|
||
}
|
||
|
||
.fuel-svg{
|
||
width: 100%;
|
||
height: auto;
|
||
background: #000;
|
||
border-radius: 10px;
|
||
}
|
||
|
||
/* jarum */
|
||
#fuel_needle_1{
|
||
transition: all 0.25s ease;
|
||
}
|
||
|
||
/* label angka bawah */
|
||
.fuel-label{
|
||
margin-top: 6px;
|
||
font-weight: bold;
|
||
color: #333;
|
||
}
|
||
|
||
/* slider tipis */
|
||
.form-range{
|
||
width: 100%;
|
||
margin-top: 6px;
|
||
}
|
||
</style>
|
||
|
||
|
||
|
||
</head>
|
||
|
||
<body>
|
||
<nav class="navbar laras-navbar px-4 py-3">
|
||
<div class="container-fluid justify-content-center">
|
||
|
||
<div class="d-flex align-items-center gap-3 text-center">
|
||
<div class="logo-wrap">
|
||
<img
|
||
src="https://raw.githubusercontent.com/ambulan119/inventarisambulans119/refs/heads/main/silarass2.ico"
|
||
alt="Logo LARAS 119"
|
||
class="laras-logo">
|
||
</div>
|
||
|
||
<div class="lh-sm text-white">
|
||
<div class="fw-bold laras-title">
|
||
SiLARAS <span class="text-119">119</span>
|
||
</div>
|
||
<div class="laras-subtitle">
|
||
Sistem Laporan Aktivitas Respon Ambulans <span class="text-119">119</span>
|
||
</div>
|
||
<div class="laras-tagline">
|
||
Selaras dalam Koordinasi, Sigap dalam Respon
|
||
<div class="laras-year">
|
||
-2026-
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</nav>
|
||
|
||
|
||
|
||
<datalist id="listPetugas"></datalist>
|
||
<div class="container my-4">
|
||
<div class="d-flex gap-2 mb-3">
|
||
<button class="btn btn-success btn-sm" onclick="authExport()">
|
||
💾 Export Data
|
||
</button>
|
||
|
||
<input type="file" id="importFile" accept=".json"
|
||
class="form-control form-control-sm w-auto">
|
||
|
||
<button class="btn btn-primary btn-sm" onclick="authImport()">
|
||
📥 Import Data
|
||
</button>
|
||
</div>
|
||
<!-- Tanggal -->
|
||
<div class="card shadow mb-4">
|
||
<div class="card-body row align-items-center">
|
||
<div class="col-md-4">
|
||
<label class="fw-bold">📅 Tanggal Laporan</label>
|
||
<input type="date" id="tanggalLaporan" class="form-control">
|
||
</div>
|
||
<div class="col-md-8 small-note mt-3 mt-md-0">
|
||
Digunakan untuk laporan kepala jaga (1 hari = 3 shift)
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- SHIFT -->
|
||
<div class="row g-4 mb-4">
|
||
<!-- SHIFT PAGI -->
|
||
<div class="col-md-4">
|
||
<div class="card card-shift text-center h-100"
|
||
onclick="animateShiftCard(this); openModalInput('Pagi')">
|
||
<div class="card-body">
|
||
<img src="https://raw.githubusercontent.com/ambulan119/inventarisambulans119/refs/heads/main/silarass2.ico"
|
||
alt="LARAS 119"
|
||
class="mb-2"
|
||
style="height:48px">
|
||
<h5 class="fw-bold text-primary mb-1">SHIFT PAGI</h5>
|
||
<div class="small text-muted">Klik untuk input laporan</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- SHIFT SORE -->
|
||
<div class="col-md-4">
|
||
<div class="card card-shift text-center h-100"
|
||
onclick="animateShiftCard(this); openModalInput('Sore')">
|
||
<div class="card-body">
|
||
<img src="https://raw.githubusercontent.com/ambulan119/inventarisambulans119/refs/heads/main/silarass2.ico"
|
||
alt="LARAS 119"
|
||
class="mb-2"
|
||
style="height:48px">
|
||
<h5 class="fw-bold text-warning mb-1">SHIFT SORE</h5>
|
||
<div class="small text-muted">Klik untuk input laporan</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- SHIFT MALAM -->
|
||
<div class="col-md-4">
|
||
<div class="card card-shift text-center h-100"
|
||
onclick="animateShiftCard(this); openModalInput('Malam')">
|
||
<div class="card-body">
|
||
<img src="https://raw.githubusercontent.com/ambulan119/inventarisambulans119/refs/heads/main/silarass2.ico"
|
||
alt="LARAS 119"
|
||
class="mb-2"
|
||
style="height:48px">
|
||
<h5 class="fw-bold text-dark mb-1">SHIFT MALAM</h5>
|
||
<div class="small text-muted">Klik untuk input laporan</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<!-- DASHBOARD HARIAN PER SHIFT -->
|
||
<div id="dashboardContainer" class="mb-4"></div>
|
||
|
||
|
||
<!-- ================= TAB LAPORAN ================= -->
|
||
<ul class="nav nav-tabs mb-3">
|
||
<li class="nav-item">
|
||
<button class="nav-link"
|
||
onclick="showTab('harian')">
|
||
📊 Laporan Harian
|
||
</button>
|
||
</li>
|
||
|
||
<li class="nav-item">
|
||
<button class="nav-link active"
|
||
onclick="showTab('mingguan')">
|
||
📅 Laporan Mingguan
|
||
</button>
|
||
</li>
|
||
|
||
<li class="nav-item">
|
||
<button class="nav-link"
|
||
onclick="showTab('bulanan')">
|
||
📆 Laporan Bulanan
|
||
</button>
|
||
</li>
|
||
</ul>
|
||
|
||
<div id="tabMingguan">
|
||
<div class="card shadow mb-4">
|
||
|
||
<!-- HEADER (tidak ikut PDF) -->
|
||
<div class="d-flex justify-content-between align-items-center mb-2 px-3 pt-3">
|
||
<h5 class="fw-bold mb-0">📅 Rekap Mingguan Ambulans 119</h5>
|
||
|
||
<button class="btn btn-sm btn-danger shadow-sm"
|
||
title="Simpan laporan mingguan dalam format PDF"
|
||
onclick="exportPDFMingguan()">
|
||
🖨️ Simpan PDF
|
||
</button>
|
||
</div>
|
||
|
||
<!-- FILTER (tidak ikut PDF) -->
|
||
<div class="card-body pt-0">
|
||
<div class="d-flex gap-2 align-items-center mb-3">
|
||
<strong>Pilih Minggu:</strong>
|
||
<input type="date" id="filterMinggu"
|
||
class="form-control form-control-sm w-auto">
|
||
<button class="btn btn-sm btn-primary"
|
||
onclick="tampilkanRekapMingguan()">
|
||
Tampilkan
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 🔽 AREA YANG DIEKSPORT KE PDF -->
|
||
<div id="areaExportMingguan" class="card-body pt-0">
|
||
<div id="rekapMingguan"></div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<div id="tabHarian" class="d-none">
|
||
|
||
<!-- TABLE -->
|
||
<div class="card shadow">
|
||
<div class="card-header fw-bold bg-white">
|
||
📊 Rekap Laporan Harian Ambulans 119
|
||
</div>
|
||
<div class="table-responsive">
|
||
<table class="table table-sm table-report align-middle mb-0">
|
||
<tbody id="tabelData"></tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div id="tabBulanan" class="d-none">
|
||
<div class="card shadow mb-4">
|
||
|
||
<!-- HEADER (TIDAK IKUT PDF) -->
|
||
<div class="card-header fw-bold bg-white d-flex justify-content-between no-print">
|
||
📆 Rekap Bulanan Ambulans 119
|
||
<button class="btn btn-sm btn-danger"
|
||
onclick="exportPDFBulanan()">
|
||
🖨️ Simpan PDF
|
||
</button>
|
||
</div>
|
||
|
||
<!-- FILTER (TIDAK IKUT PDF) -->
|
||
<div class="card-body no-print">
|
||
<div class="d-flex gap-2 align-items-center mb-3">
|
||
<strong>Pilih Bulan:</strong>
|
||
<input type="month" id="filterBulan"
|
||
class="form-control form-control-sm w-auto">
|
||
<button class="btn btn-sm btn-primary"
|
||
onclick="tampilkanRekapBulanan()">
|
||
Tampilkan
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ✅ AREA EXPORT PDF -->
|
||
<div id="areaExportBulanan" class="card-body pt-0">
|
||
<div id="rekapBulanan"></div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<!-- MODAL -->
|
||
<div class="modal fade" id="modalInput">
|
||
<div class="modal-dialog modal-lg modal-dialog-centered">
|
||
<div class="modal-content">
|
||
|
||
<div class="modal-header bg-primary text-white">
|
||
<h5 class="modal-title">Input Laporan Shift</h5>
|
||
<button class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
|
||
<div class="modal-body">
|
||
|
||
<input type="hidden" id="shift">
|
||
|
||
<div class="mb-2">
|
||
<label class="fw-bold">Jenis Laporan</label>
|
||
<select id="jenis" class="form-select" onchange="onJenisChange()">
|
||
<option value="">-- Pilih --</option>
|
||
<optgroup label="🚑 Pelayanan Ambulans">
|
||
<option value="RFDK">RFDK – Rumah ke Faskes Dalam Kota</option>
|
||
<option value="F-FDK">F-FDK – Fasilitas Umum ke Faskes Dalam Kota</option>
|
||
<option value="AFDK">AFDK – Antar Faskes Dalam Kota</option>
|
||
<option value="AFLK">AFLK – Antar Faskes Luar Kota</option>
|
||
<option value="PDK">PDK – Pulang Dalam Kota</option>
|
||
<option value="PLK">PLK – Pulang Luar Kota</option>
|
||
<option value="MSDT">MSDT – Meninggal Saat Transfer</option>
|
||
<option value="MLDPA">MLDPA – Meninggal di Lokasi Pelayanan</option>
|
||
</optgroup>
|
||
|
||
<optgroup label="🧑⚕️ Aktivitas Petugas">
|
||
<option value="AIRS">AIRS – Aktivitas Internal RSSA</option>
|
||
<option value="AIDA">AIDA – Aktivitas Internal Dengan Ambulans</option>
|
||
<option value="MSAD">MSAD – Meninggal Sebelum Ambulans Datang</option>
|
||
<option value="BATAL">Permintaan Tidak Terlayani</option>
|
||
</optgroup>
|
||
|
||
|
||
<optgroup label="🎪 Standby Event">
|
||
<option value="EVENT">Standby Event</option>
|
||
</optgroup>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="mb-2 d-none" id="boxEmergency">
|
||
<label class="fw-bold">Status Kasus</label>
|
||
<select id="emergency" class="form-select">
|
||
<option value="">-- Pilih --</option>
|
||
<option value="true">🔴 True Emergency</option>
|
||
<option value="false">🟢 Non Emergency</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="mb-2">
|
||
<label class="fw-bold">Wilayah</label>
|
||
<select id="wilayah" class="form-select">
|
||
<option value="">-- Pilih --</option>
|
||
<option>Dalam Kota</option>
|
||
<option>Luar Kota</option>
|
||
<option>Internal</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="mb-2" id="boxTujuan">
|
||
<label class="fw-bold">Tujuan / Keterangan</label>
|
||
<input type="text" id="tujuan" class="form-control" placeholder="Nama RS / Wilayah / Tujuan">
|
||
</div>
|
||
|
||
<div id="boxEvent" class="d-none">
|
||
|
||
<div class="mb-2">
|
||
<label class="fw-bold">Nama Event</label>
|
||
<input type="text" id="namaEvent" class="form-control">
|
||
</div>
|
||
|
||
<div class="mb-2">
|
||
<label class="fw-bold">Penyelenggara</label>
|
||
<input type="text" id="penyelenggaraEvent" class="form-control">
|
||
</div>
|
||
|
||
<div class="mb-2">
|
||
<label class="fw-bold">Lokasi Event</label>
|
||
<input type="text" id="lokasiEvent" class="form-control">
|
||
</div>
|
||
|
||
<div class="mb-2">
|
||
<label class="fw-bold">Jumlah Pasien</label>
|
||
<input type="number" id="jumlahPasien"
|
||
class="form-control"
|
||
min="0"
|
||
placeholder="Kosongkan jika tidak ada pasien">
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div class="mb-2">
|
||
<label class="fw-bold">Derajat Transfer Pasien <span class="text-danger">*</span></label>
|
||
<select id="derajat" class="form-select">
|
||
<option value="">-- Wajib diisi --</option>
|
||
<option value="NON">Tidak dilakukan transfer pasien</option>
|
||
<option value="0.5">0.5 – Operator Layanan Operasional</option>
|
||
<option value="1">1 – Perawat</option>
|
||
<option value="2">2 – Perawat + Dokter</option>
|
||
<option value="3">3 – Perawat + Dokter Spesialis Emergensi / Anestesi</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="mb-2">
|
||
<label class="fw-bold">Keterangan / Diagnosa / Alasan</label>
|
||
<textarea id="catatan" class="form-control" rows="2" spellcheck="false"></textarea>
|
||
</div>
|
||
<hr>
|
||
<hr>
|
||
<h6 class="fw-bold">👥 Petugas Shift</h6>
|
||
|
||
<div class="mb-2">
|
||
<label class="fw-bold">Kepala Jaga</label>
|
||
<input type="text" class="form-control" id="kepalaJaga" list="listPetugas">
|
||
</div>
|
||
|
||
<div class="mb-2">
|
||
<label class="fw-bold">Perawat Pelaksana</label>
|
||
<div id="perawatContainer"></div>
|
||
<button type="button" class="btn btn-sm btn-outline-primary mt-1"
|
||
onclick="tambahPerawat()">+ Tambah Perawat</button>
|
||
</div>
|
||
|
||
<div class="mb-2">
|
||
<label class="fw-bold">Operator Layanan Operasional</label>
|
||
<div id="operatorContainer"></div>
|
||
<button type="button" class="btn btn-sm btn-outline-primary mt-1"
|
||
onclick="tambahOperator()">+ Tambah Operator</button>
|
||
</div>
|
||
|
||
|
||
</div>
|
||
|
||
<div class="modal-footer justify-content-between">
|
||
<button class="btn btn-danger d-none" id="btnHapusKasus"
|
||
onclick="hapusKasus(document.getElementById('modalInput').dataset.editId)">
|
||
🗑️ Hapus Kasus
|
||
</button>
|
||
|
||
<div>
|
||
<button class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
|
||
<button class="btn btn-primary" onclick="simpan()">Simpan</button>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="modal fade" id="modalKendala">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
|
||
<div class="modal-header">
|
||
<h5 class="modal-title">✏️ Edit Kendala Operasional</h5>
|
||
</div>
|
||
|
||
<div class="modal-body">
|
||
<input type="hidden" id="kendalaId">
|
||
|
||
<div class="mb-2">
|
||
<label>Tanggal</label>
|
||
<input type="date" id="kendalaTanggal" class="form-control">
|
||
</div>
|
||
|
||
<div class="mb-2">
|
||
<label>Shift</label>
|
||
<select id="kendalaShift" class="form-select">
|
||
<option>Pagi</option>
|
||
<option>Sore</option>
|
||
<option>Malam</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="mb-2">
|
||
<label>Catatan / Kendala</label>
|
||
<textarea id="kendalaIsi" class="form-control"></textarea>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="modal-footer">
|
||
<button class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
|
||
<button class="btn btn-success" onclick="simpanEditKendala()">Simpan</button>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ================= MODAL ARMADA ================= -->
|
||
<div class="modal fade" id="modalArmada">
|
||
<div class="modal-dialog modal-lg">
|
||
<div class="modal-content">
|
||
|
||
<div class="modal-header bg-dark text-white">
|
||
<h5 class="modal-title">🚑 Input Armada Shift</h5>
|
||
<button class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
|
||
<div class="modal-body">
|
||
|
||
<!-- ================= AE 1 - PREMIO ================= -->
|
||
<div class="card mb-3 shadow-sm">
|
||
<div class="card-body">
|
||
<h6 class="fw-bold">🚑 AE 1 - PREMIO</h6>
|
||
|
||
<label class="mt-2">Kondisi Kendaraan</label>
|
||
<select class="form-select form-select-sm" id="status_1">
|
||
<option value="">---</option>
|
||
<option value="siap">Siap Operasional</option>
|
||
<option value="maintenance">Dalam Perawatan</option>
|
||
<option value="tidak_layak">Tidak Laik Operasional</option>
|
||
</select>
|
||
|
||
<!-- BBM -->
|
||
<div class="fuel-semi">
|
||
<label class="mt-2">BBM</label>
|
||
<svg viewBox="0 0 200 120" class="fuel-svg">
|
||
|
||
<!-- ARC BACKGROUND -->
|
||
<path d="M10 110 A90 90 0 0 1 190 110"
|
||
stroke="#2b2b2b"
|
||
stroke-width="12"
|
||
fill="none"
|
||
stroke-linecap="round"/>
|
||
|
||
<!-- LABEL 0 -->
|
||
<text x="10" y="115" fill="#fff" font-size="12" font-weight="bold">0</text>
|
||
|
||
<!-- LABEL 1/2 -->
|
||
<text x="90" y="35" fill="#fff" font-size="12" font-weight="bold">1/2</text>
|
||
|
||
<!-- FUEL ICON -->
|
||
<text x="170" y="115" fill="#fff" font-size="14">⛽</text>
|
||
|
||
<!-- NEEDLE -->
|
||
<line id="fuel_needle_1"
|
||
x1="100" y1="110"
|
||
x2="100" y2="30"
|
||
stroke="red"
|
||
stroke-width="5"
|
||
stroke-linecap="round"/>
|
||
|
||
<!-- CENTER CAP -->
|
||
<circle cx="100" cy="110" r="8" fill="#111" stroke="#555" stroke-width="2"/>
|
||
|
||
</svg>
|
||
|
||
<input type="range"
|
||
min="0"
|
||
max="4"
|
||
step="0.1"
|
||
id="bbm_1"
|
||
oninput="updateFuelSemi(this)"
|
||
class="form-range"/>
|
||
|
||
<div class="fuel-label" id="fuel_val_1">0</div>
|
||
|
||
</div>
|
||
|
||
<label>Kilometer</label>
|
||
<input type="number" class="form-control form-control-sm mb-2" id="km_1" placeholder="KM">
|
||
|
||
<!-- ================= OXYGEN ================= -->
|
||
<label class="fw-bold mt-2">
|
||
🫁 Oksigen
|
||
</label>
|
||
<div class="row g-2 mt-1">
|
||
<!-- ================= TABUNG 1 ================= -->
|
||
<div class="col-4">
|
||
|
||
<div class="oxy-card text-center">
|
||
<label class="form-label small">Tabung Oksigen Central 1</label>
|
||
<div class="knob-wrap">
|
||
<input
|
||
type="range"
|
||
min="0"
|
||
max="150"
|
||
step="1"
|
||
value="150"
|
||
id="oxy1_1"
|
||
class="oxy-knob"
|
||
data-fill="fill1_1"
|
||
data-text="text1_1"
|
||
data-status="status1_1">
|
||
<div class="knob-value" id="text1_1">
|
||
150 Kgf/cm³
|
||
</div>
|
||
</div>
|
||
<!-- TABUNG -->
|
||
<div class="oxy-cylinder">
|
||
<div
|
||
class="oxy-fill"
|
||
id="fill1_1"
|
||
style="
|
||
height:100%;
|
||
background:
|
||
linear-gradient(
|
||
to top,
|
||
#16a085,
|
||
#2ecc71
|
||
);
|
||
">
|
||
</div>
|
||
</div>
|
||
<!-- STATUS -->
|
||
<div
|
||
class="oxy-status text-success"
|
||
id="status1_1">
|
||
🟢 FULL
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ================= TABUNG 2 ================= -->
|
||
<div class="col-4">
|
||
<div class="oxy-card text-center">
|
||
<label class="form-label small">Tabung Oksigen Central 2</label>
|
||
<div class="knob-wrap">
|
||
<input
|
||
type="range"
|
||
min="0"
|
||
max="150"
|
||
step="1"
|
||
value="150"
|
||
id="oxy1_2"
|
||
class="oxy-knob"
|
||
data-fill="fill1_2"
|
||
data-text="text1_2"
|
||
data-status="status1_2">
|
||
<div class="knob-value" id="text1_2">
|
||
150 Kgf/cm³
|
||
</div>
|
||
</div>
|
||
<!-- TABUNG -->
|
||
<div class="oxy-cylinder">
|
||
<div
|
||
class="oxy-fill"
|
||
id="fill1_2"
|
||
style="
|
||
height:100%;
|
||
background:
|
||
linear-gradient(
|
||
to top,
|
||
#16a085,
|
||
#2ecc71
|
||
);
|
||
">
|
||
</div>
|
||
|
||
</div>
|
||
<!-- STATUS -->
|
||
<div
|
||
class="oxy-status text-success"
|
||
id="status1_2">
|
||
🟢 FULL
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ================= TABUNG 3 ================= -->
|
||
<div class="col-4">
|
||
<div class="oxy-card text-center">
|
||
<label class="form-label small">Tabung Oksigen Transport</label>
|
||
<div class="knob-wrap">
|
||
<input
|
||
type="range"
|
||
min="0"
|
||
max="150"
|
||
step="1"
|
||
value="150"
|
||
id="oxy1_3"
|
||
class="oxy-knob"
|
||
data-fill="fill1_3"
|
||
data-text="text1_3"
|
||
data-status="status1_3">
|
||
<div class="knob-value" id="text1_3">
|
||
150 Kgf/cm³
|
||
</div>
|
||
</div>
|
||
<!-- TABUNG -->
|
||
<div class="oxy-cylinder">
|
||
<div
|
||
class="oxy-fill"
|
||
id="fill1_3"
|
||
style="
|
||
height:100%;
|
||
background:
|
||
linear-gradient(
|
||
to top,
|
||
#16a085,
|
||
#2ecc71
|
||
);
|
||
">
|
||
</div>
|
||
</div>
|
||
<!-- STATUS -->
|
||
<div
|
||
class="oxy-status text-success"
|
||
id="status1_3">
|
||
🟢 FULL
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ================= CADANGAN ================= -->
|
||
<div class="col-12 mt-2">
|
||
<label class="small fw-bold">
|
||
🧯 Tabung Oksigen Cadangan
|
||
</label>
|
||
<select
|
||
class="form-select form-select-sm"
|
||
id="oxy1_cadangan">
|
||
<option value="tersegel">
|
||
🟢 Tersegel
|
||
</option>
|
||
<option value="kosong">
|
||
🔴 Kosong
|
||
</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ================= AE 2 - COMMUTER ================= -->
|
||
<div class="card mb-3 shadow-sm">
|
||
<div class="card-body">
|
||
<h6 class="fw-bold">🚑 AE 2 - COMMUTER</h6>
|
||
|
||
<label class="mt-2">Kondisi Kendaraan</label>
|
||
<select class="form-select form-select-sm" id="status_2">
|
||
<option value="">---</option>
|
||
<option value="siap">Siap Operasional</option>
|
||
<option value="maintenance">Dalam Perawatan</option>
|
||
<option value="tidak_layak">Tidak Laik Operasional</option>
|
||
</select>
|
||
|
||
<!-- BBM -->
|
||
<div class="fuel-semi">
|
||
<label class="mt-2">BBM</label>
|
||
<svg viewBox="0 0 200 120" class="fuel-svg">
|
||
|
||
<!-- ARC BACKGROUND -->
|
||
<path d="M10 110 A90 90 0 0 1 190 110"
|
||
stroke="#2b2b2b"
|
||
stroke-width="12"
|
||
fill="none"
|
||
stroke-linecap="round"/>
|
||
|
||
<!-- LABEL 0 -->
|
||
<text x="10" y="115" fill="#fff" font-size="12" font-weight="bold">0</text>
|
||
|
||
<!-- LABEL 1/2 -->
|
||
<text x="90" y="35" fill="#fff" font-size="12" font-weight="bold">1/2</text>
|
||
|
||
<!-- FUEL ICON -->
|
||
<text x="170" y="115" fill="#fff" font-size="14">⛽</text>
|
||
|
||
<!-- NEEDLE -->
|
||
<line id="fuel_needle_2"
|
||
x1="100" y1="110"
|
||
x2="100" y2="30"
|
||
stroke="red"
|
||
stroke-width="5"
|
||
stroke-linecap="round"/>
|
||
|
||
<!-- CENTER CAP -->
|
||
<circle cx="100" cy="110" r="8" fill="#111" stroke="#555" stroke-width="2"/>
|
||
|
||
</svg>
|
||
|
||
<input type="range"
|
||
min="0"
|
||
max="4"
|
||
step="0.1"
|
||
id="bbm_2"
|
||
oninput="updateFuelSemi(this)"
|
||
class="form-range"/>
|
||
|
||
<div class="fuel-label" id="fuel_val_2">0</div>
|
||
|
||
</div>
|
||
|
||
<label>Kilometer</label>
|
||
<input type="number" class="form-control form-control-sm mb-2" id="km_2" placeholder="KM">
|
||
|
||
<!-- ================= OXYGEN ================= -->
|
||
<label class="fw-bold mt-2">
|
||
🫁 Oksigen
|
||
</label>
|
||
<div class="row g-2 mt-1">
|
||
<!-- ================= TABUNG 1 ================= -->
|
||
<div class="col-4">
|
||
<div class="oxy-card text-center">
|
||
<label class="form-label small">Tabung Oksigen Central 1</label>
|
||
<div class="knob-wrap">
|
||
<input
|
||
type="range"
|
||
min="0"
|
||
max="150"
|
||
step="1"
|
||
value="150"
|
||
id="oxy2_1"
|
||
class="oxy-knob"
|
||
data-fill="fill2_1"
|
||
data-text="text2_1"
|
||
data-status="status2_1">
|
||
<div class="knob-value" id="text2_1">
|
||
150 Kgf/cm³
|
||
</div>
|
||
</div>
|
||
<!-- TABUNG -->
|
||
<div class="oxy-cylinder">
|
||
<div
|
||
class="oxy-fill"
|
||
id="fill2_1"
|
||
style="
|
||
height:100%;
|
||
background:
|
||
linear-gradient(
|
||
to top,
|
||
#16a085,
|
||
#2ecc71
|
||
);
|
||
">
|
||
</div>
|
||
</div>
|
||
<!-- STATUS -->
|
||
<div
|
||
class="oxy-status text-success"
|
||
id="status2_1">
|
||
🟢 FULL
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ================= TABUNG 2 ================= -->
|
||
<div class="col-4">
|
||
<div class="oxy-card text-center">
|
||
<label class="form-label small">Tabung Oksigen Central 2</label>
|
||
<div class="knob-wrap">
|
||
<input
|
||
type="range"
|
||
min="0"
|
||
max="150"
|
||
step="1"
|
||
value="150"
|
||
id="oxy2_2"
|
||
class="oxy-knob"
|
||
data-fill="fill2_2"
|
||
data-text="text2_2"
|
||
data-status="status2_2">
|
||
<div class="knob-value" id="text2_2">
|
||
150 Kgf/cm³
|
||
</div>
|
||
</div>
|
||
<!-- TABUNG -->
|
||
<div class="oxy-cylinder">
|
||
<div
|
||
class="oxy-fill"
|
||
id="fill2_2"
|
||
style="
|
||
height:100%;
|
||
background:
|
||
linear-gradient(
|
||
to top,
|
||
#16a085,
|
||
#2ecc71
|
||
);
|
||
">
|
||
</div>
|
||
</div>
|
||
<!-- STATUS -->
|
||
<div
|
||
class="oxy-status text-success"
|
||
id="status2_2">
|
||
🟢 FULL
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ================= TABUNG 3 ================= -->
|
||
<div class="col-4">
|
||
<div class="oxy-card text-center">
|
||
<label class="form-label small">Tabung Oksigen Transport</label>
|
||
<div class="knob-wrap">
|
||
<input
|
||
type="range"
|
||
min="0"
|
||
max="150"
|
||
step="1"
|
||
value="150"
|
||
id="oxy2_3"
|
||
class="oxy-knob"
|
||
data-fill="fill2_3"
|
||
data-text="text2_3"
|
||
data-status="status2_3">
|
||
<div class="knob-value" id="text2_3">
|
||
150 Kgf/cm³
|
||
</div>
|
||
</div>
|
||
<!-- TABUNG -->
|
||
<div class="oxy-cylinder">
|
||
<div
|
||
class="oxy-fill"
|
||
id="fill2_3"
|
||
style="
|
||
height:100%;
|
||
background:
|
||
linear-gradient(
|
||
to top,
|
||
#16a085,
|
||
#2ecc71
|
||
);
|
||
">
|
||
</div>
|
||
</div>
|
||
<!-- STATUS -->
|
||
<div
|
||
class="oxy-status text-success"
|
||
id="status2_3">
|
||
🟢 FULL
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ================= CADANGAN ================= -->
|
||
<div class="col-12 mt-2">
|
||
<label class="small fw-bold">
|
||
🧯 Tabung Oksigen Cadangan
|
||
</label>
|
||
<select
|
||
class="form-select form-select-sm"
|
||
id="oxy2_cadangan">
|
||
<option value="tersegel">
|
||
🟢 Tersegel
|
||
</option>
|
||
<option value="kosong">
|
||
🔴 Kosong
|
||
</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ================= AE 3 - ELF ================= -->
|
||
<div class="card mb-3 shadow-sm">
|
||
<div class="card-body">
|
||
<h6 class="fw-bold">🚑 AE 3 - ELF</h6>
|
||
|
||
<label class="mt-2">Kondisi Kendaraan</label>
|
||
<select class="form-select form-select-sm" id="status_3">
|
||
<option value="">---</option>
|
||
<option value="siap">Siap Operasional</option>
|
||
<option value="maintenance">Dalam Perawatan</option>
|
||
<option value="tidak_layak">Tidak Laik Operasional</option>
|
||
</select>
|
||
|
||
<!-- BBM -->
|
||
<div class="fuel-semi">
|
||
<label class="mt-2">BBM</label>
|
||
<svg viewBox="0 0 200 120" class="fuel-svg">
|
||
|
||
<!-- ARC BACKGROUND -->
|
||
<path d="M10 110 A90 90 0 0 1 190 110"
|
||
stroke="#2b2b2b"
|
||
stroke-width="12"
|
||
fill="none"
|
||
stroke-linecap="round"/>
|
||
|
||
<!-- LABEL 0 -->
|
||
<text x="10" y="115" fill="#fff" font-size="12" font-weight="bold">0</text>
|
||
|
||
<!-- LABEL 1/2 -->
|
||
<text x="90" y="35" fill="#fff" font-size="12" font-weight="bold">1/2</text>
|
||
|
||
<!-- FUEL ICON -->
|
||
<text x="170" y="115" fill="#fff" font-size="14">⛽</text>
|
||
|
||
<!-- NEEDLE -->
|
||
<line id="fuel_needle_3"
|
||
x1="100" y1="110"
|
||
x2="100" y2="30"
|
||
stroke="red"
|
||
stroke-width="5"
|
||
stroke-linecap="round"/>
|
||
|
||
<!-- CENTER CAP -->
|
||
<circle cx="100" cy="110" r="8" fill="#111" stroke="#555" stroke-width="2"/>
|
||
|
||
</svg>
|
||
|
||
<input type="range"
|
||
min="0"
|
||
max="4"
|
||
step="0.1"
|
||
id="bbm_3"
|
||
oninput="updateFuelSemi(this)"
|
||
class="form-range"/>
|
||
|
||
<div class="fuel-label" id="fuel_val_3">0</div>
|
||
|
||
</div>
|
||
|
||
<label>Kilometer</label>
|
||
<input type="number" class="form-control form-control-sm mb-2" id="km_3" placeholder="KM">
|
||
|
||
<!-- ================= OXYGEN ================= -->
|
||
<label class="fw-bold mt-2">
|
||
🫁 Oksigen
|
||
</label>
|
||
<div class="row g-2 mt-1">
|
||
<!-- ================= TABUNG 1 ================= -->
|
||
<div class="col-4">
|
||
<div class="oxy-card text-center">
|
||
<label class="form-label small">Tabung Oksigen Central 1</label>
|
||
<div class="knob-wrap">
|
||
<input
|
||
type="range"
|
||
min="0"
|
||
max="150"
|
||
step="1"
|
||
value="150"
|
||
id="oxy3_1"
|
||
class="oxy-knob"
|
||
data-fill="fill3_1"
|
||
data-text="text3_1"
|
||
data-status="status3_1">
|
||
<div class="knob-value" id="text3_1">
|
||
150 Kgf/cm³
|
||
</div>
|
||
</div>
|
||
<!-- TABUNG -->
|
||
<div class="oxy-cylinder">
|
||
<div
|
||
class="oxy-fill"
|
||
id="fill3_1"
|
||
style="
|
||
height:100%;
|
||
background:
|
||
linear-gradient(
|
||
to top,
|
||
#16a085,
|
||
#2ecc71
|
||
);
|
||
">
|
||
</div>
|
||
</div>
|
||
|
||
<!-- STATUS -->
|
||
<div
|
||
class="oxy-status text-success"
|
||
id="status3_1">
|
||
🟢 FULL
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ================= TABUNG 2 ================= -->
|
||
<div class="col-4">
|
||
<div class="oxy-card text-center">
|
||
<label class="form-label small">Tabung Oksigen Central 2</label>
|
||
<div class="knob-wrap">
|
||
<input
|
||
type="range"
|
||
min="0"
|
||
max="150"
|
||
step="1"
|
||
value="150"
|
||
id="oxy3_2"
|
||
class="oxy-knob"
|
||
data-fill="fill3_2"
|
||
data-text="text3_2"
|
||
data-status="status3_2">
|
||
<div class="knob-value" id="text3_2">
|
||
150 Kgf/cm³
|
||
</div>
|
||
</div>
|
||
<!-- TABUNG -->
|
||
<div class="oxy-cylinder">
|
||
<div
|
||
class="oxy-fill"
|
||
id="fill3_2"
|
||
style="
|
||
height:100%;
|
||
background:
|
||
linear-gradient(
|
||
to top,
|
||
#16a085,
|
||
#2ecc71
|
||
);
|
||
">
|
||
</div>
|
||
</div>
|
||
|
||
<!-- STATUS -->
|
||
<div
|
||
class="oxy-status text-success"
|
||
id="status3_2">
|
||
🟢 FULL
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ================= TABUNG 3 ================= -->
|
||
<div class="col-4">
|
||
<div class="oxy-card text-center">
|
||
<label class="form-label small">Tabung Oksigen Transport</label>
|
||
<div class="knob-wrap">
|
||
<input
|
||
type="range"
|
||
min="0"
|
||
max="150"
|
||
step="1"
|
||
value="150"
|
||
id="oxy3_3"
|
||
class="oxy-knob"
|
||
data-fill="fill3_3"
|
||
data-text="text3_3"
|
||
data-status="status3_3">
|
||
<div class="knob-value" id="text3_3">
|
||
150 Kgf/cm³
|
||
</div>
|
||
</div>
|
||
<!-- TABUNG -->
|
||
<div class="oxy-cylinder">
|
||
<div
|
||
class="oxy-fill"
|
||
id="fill3_3"
|
||
style="
|
||
height:100%;
|
||
background:
|
||
linear-gradient(
|
||
to top,
|
||
#16a085,
|
||
#2ecc71
|
||
);
|
||
">
|
||
</div>
|
||
</div>
|
||
<!-- STATUS -->
|
||
<div
|
||
class="oxy-status text-success"
|
||
id="status3_3">
|
||
🟢 FULL
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- ================= CADANGAN ================= -->
|
||
<div class="col-12 mt-2">
|
||
<label class="small fw-bold">
|
||
🧯 Tabung Oksigen Cadangan
|
||
</label>
|
||
<select
|
||
class="form-select form-select-sm"
|
||
id="oxy3_cadangan">
|
||
<option value="tersegel">
|
||
🟢 Tersegel
|
||
</option>
|
||
<option value="kosong">
|
||
🔴 Kosong
|
||
</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div class="modal-footer">
|
||
<button class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
|
||
<button class="btn btn-success" onclick="simpanArmada()">💾 Simpan</button>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf-autotable/3.5.29/jspdf.plugin.autotable.min.js"></script>
|
||
|
||
<script>
|
||
const ARMADA = [
|
||
{ kode: 1, nama: "AE 1 - PREMIO" },
|
||
{ kode: 2, nama: "AE 2 - COMMUTER" },
|
||
{ kode: 3, nama: "AE 3 - ELF" }
|
||
];
|
||
|
||
function safe(v){
|
||
return (v === undefined || v === null || v === "") ? "-" : v;
|
||
}
|
||
|
||
let modalMode = "input"; // input | petugas
|
||
const shiftInput = document.getElementById("shift");
|
||
const tanggalInput = document.getElementById("tanggalLaporan");
|
||
|
||
function setToday(){
|
||
const today = new Date().toISOString().split("T")[0];
|
||
tanggalInput.value = today;
|
||
}
|
||
|
||
setToday();
|
||
updatePetugasList();
|
||
|
||
let modal = new bootstrap.Modal(document.getElementById('modalInput'));
|
||
const modalFormHTML =
|
||
document.querySelector("#modalInput .modal-body").innerHTML;
|
||
|
||
modal._element.addEventListener('hidden.bs.modal', () => {
|
||
document.querySelector("#modalInput .modal-body").innerHTML = modalFormHTML;
|
||
document.getElementById("btnHapusKasus").classList.add("d-none");
|
||
delete document.getElementById("modalInput").dataset.editId;
|
||
});
|
||
|
||
const JENIS_LABEL = {
|
||
"RFDK": "Rumah ke Faskes Dalam Kota",
|
||
"F-FDK": "Fasilitas Umum ke Faskes Dalam Kota",
|
||
"AFDK": "Antar Faskes Dalam Kota",
|
||
"AFLK": "Antar Faskes Luar Kota",
|
||
"PDK": "Pulang Dalam Kota",
|
||
"PLK": "Pulang Luar Kota",
|
||
"MSDT": "Meninggal Saat Dalam Transfer",
|
||
"MLDPA": "Meninggal di Lokasi Dalam Pelayanan Ambulans",
|
||
"MSAD": "Meninggal Sebelum Ambulans Datang",
|
||
"AIRS": "Aktivitas Internal RSSA",
|
||
"AIDA": "Aktivitas Internal Dengan Ambulans",
|
||
"BATAL": "Permintaan Ambulans Tidak Terlayani",
|
||
"EVENT": "Standby Event",
|
||
|
||
};
|
||
|
||
function openModal(s){
|
||
updatePetugasList();
|
||
|
||
if(modalMode === "petugas"){
|
||
// sembunyikan input kasus
|
||
jenis.closest(".mb-2").classList.add("d-none");
|
||
boxEmergency.classList.add("d-none");
|
||
wilayah.closest(".mb-2").classList.add("d-none");
|
||
boxTujuan.classList.add("d-none");
|
||
derajat.closest(".mb-2").classList.add("d-none");
|
||
catatan.closest(".mb-2").classList.add("d-none");
|
||
} else {
|
||
// tampilkan kembali
|
||
jenis.closest(".mb-2").classList.remove("d-none");
|
||
wilayah.closest(".mb-2").classList.remove("d-none");
|
||
derajat.closest(".mb-2").classList.remove("d-none");
|
||
catatan.closest(".mb-2").classList.remove("d-none");
|
||
}
|
||
|
||
shiftInput.value = s;
|
||
|
||
// reset input laporan
|
||
if(modalMode === "input"){
|
||
jenis.value = "";
|
||
wilayah.value = "";
|
||
tujuan.value = "";
|
||
derajat.value = "";
|
||
emergency.value = "";
|
||
catatan.value = "";
|
||
}
|
||
|
||
boxEmergency.classList.add('d-none');
|
||
|
||
// ambil meta shift
|
||
const metaKey = "shiftMeta_" + tanggalInput.value + "_" + s;
|
||
const meta = JSON.parse(localStorage.getItem(metaKey));
|
||
|
||
// reset container
|
||
perawatContainer.innerHTML = "";
|
||
operatorContainer.innerHTML = "";
|
||
|
||
if(meta){
|
||
kepalaJaga.value = meta.kepalaJaga || "";
|
||
|
||
(meta.perawat.length ? meta.perawat : [""])
|
||
.forEach(p=>{
|
||
perawatContainer.insertAdjacentHTML("beforeend",
|
||
`<input type="text" class="form-control mb-1 perawat"
|
||
list="listPetugas" value="${p}">`
|
||
);
|
||
});
|
||
|
||
(meta.operator.length ? meta.operator : [""])
|
||
.forEach(o=>{
|
||
operatorContainer.insertAdjacentHTML("beforeend",
|
||
`<input type="text" class="form-control mb-1 operator"
|
||
list="listPetugas" value="${o}">`
|
||
);
|
||
});
|
||
|
||
} else {
|
||
kepalaJaga.value = "";
|
||
tambahPerawat();
|
||
tambahOperator();
|
||
}
|
||
|
||
modal.show();
|
||
|
||
}
|
||
|
||
|
||
function onJenisChange() {
|
||
const j = jenis.value;
|
||
|
||
// reset tampilan
|
||
boxEmergency.classList.add("d-none");
|
||
boxEvent.classList.add("d-none");
|
||
boxTujuan.classList.remove("d-none");
|
||
|
||
emergency.value = "";
|
||
wilayah.value = "";
|
||
|
||
// ================= EVENT =================
|
||
if (j === "EVENT") {
|
||
boxEvent.classList.remove("d-none");
|
||
|
||
// sembunyikan field yg tidak relevan
|
||
boxEmergency.classList.add("d-none");
|
||
boxTujuan.classList.add("d-none");
|
||
derajat.closest(".mb-2").classList.add("d-none");
|
||
|
||
wilayah.value = ""; // bebas pilih
|
||
emergency.value = "-";
|
||
derajat.value = "NON";
|
||
return;
|
||
}
|
||
|
||
// tampilkan kembali derajat kalau bukan EVENT
|
||
derajat.closest(".mb-2").classList.remove("d-none");
|
||
|
||
// ================= WILAYAH OTOMATIS =================
|
||
const dalamKota = ["RFDK","F-FDK","AFDK","PDK"];
|
||
const luarKota = ["AFLK","PLK"];
|
||
const internal = ["AIRS","AIDA"];
|
||
|
||
if (dalamKota.includes(j)) wilayah.value = "Dalam Kota";
|
||
if (luarKota.includes(j)) wilayah.value = "Luar Kota";
|
||
if (internal.includes(j)) wilayah.value = "Internal";
|
||
|
||
// ================= EMERGENCY =================
|
||
const autoTrue = ["MSDT","MSAD","MLDPA"];
|
||
const autoNon = ["BATAL"];
|
||
const manual = ["RFDK","F-FDK","AFDK","AFLK","PDK","PLK","AIRS","AIDA"];
|
||
|
||
if (autoTrue.includes(j)) {
|
||
emergency.value = "true";
|
||
}
|
||
else if (autoNon.includes(j)) {
|
||
emergency.value = "false";
|
||
}
|
||
else if (manual.includes(j)) {
|
||
boxEmergency.classList.remove("d-none");
|
||
}
|
||
}
|
||
|
||
|
||
|
||
function hitungStatus(jenis){
|
||
if (jenis === "BATAL") return "Batal";
|
||
return "Dilayani";
|
||
}
|
||
|
||
function simpan(){
|
||
|
||
// ===== SIMPAN PETUGAS (SELALU) =====
|
||
let metaKey = "shiftMeta_" + tanggalInput.value + "_" + shiftInput.value;
|
||
|
||
localStorage.setItem(metaKey, JSON.stringify({
|
||
kepalaJaga: kepalaJaga.value || "-",
|
||
perawat: [...document.querySelectorAll(".perawat")]
|
||
.map(e => e.value).filter(Boolean),
|
||
operator: [...document.querySelectorAll(".operator")]
|
||
.map(e => e.value).filter(Boolean)
|
||
}));
|
||
|
||
if (modalMode === "editShift") {
|
||
modal.hide();
|
||
return;
|
||
}
|
||
|
||
if (jenis.value === "EVENT") {
|
||
if (!namaEvent.value.trim() || !lokasiEvent.value.trim()) {
|
||
alert("Nama dan lokasi event wajib diisi");
|
||
return;
|
||
}
|
||
}
|
||
|
||
// ===== JIKA MODE PETUGAS → SELESAI =====
|
||
if(modalMode === "petugas"){
|
||
modal.hide();
|
||
tampil();
|
||
return;
|
||
}
|
||
|
||
// ===== MODE INPUT KASUS (LANJUT) =====
|
||
if(!jenis.value || !wilayah.value){
|
||
alert("Lengkapi data wajib!");
|
||
return;
|
||
}
|
||
|
||
if (!derajat.value) {
|
||
alert("Derajat Transfer Pasien wajib ditentukan!");
|
||
return;
|
||
}
|
||
|
||
if (!boxTujuan.classList.contains('d-none') && !tujuan.value.trim()) {
|
||
alert("Tujuan / Keterangan wajib diisi!");
|
||
return;
|
||
}
|
||
|
||
if(!boxEmergency.classList.contains('d-none') && !emergency.value){
|
||
alert("Pilih True / Non Emergency");
|
||
return;
|
||
}
|
||
|
||
let db = JSON.parse(localStorage.getItem("ambulans2026") || "[]");
|
||
|
||
if (modalMode === "editKasus") {
|
||
|
||
const editId = document
|
||
.getElementById("modalInput")
|
||
.dataset.editId;
|
||
|
||
db = db.map(d=>{
|
||
if(d.id === editId){
|
||
|
||
if(d.jenis === "EVENT"){
|
||
return {
|
||
...d,
|
||
namaEvent: namaEvent.value,
|
||
penyelenggara: penyelenggaraEvent.value || "-",
|
||
lokasiEvent: lokasiEvent.value,
|
||
wilayah: wilayah.value || "-",
|
||
jumlahPasien: jumlahPasien.value
|
||
? parseInt(jumlahPasien.value)
|
||
: 0,
|
||
keterangan: catatan.value || "-"
|
||
};
|
||
}
|
||
|
||
return {
|
||
...d,
|
||
jenis: jenis.value,
|
||
emergency: emergency.value || "-",
|
||
wilayah: wilayah.value,
|
||
tujuan: tujuan.value || "-",
|
||
derajat: derajat.value || "-",
|
||
status: hitungStatus(jenis.value),
|
||
catatan: catatan.value
|
||
};
|
||
}
|
||
return d;
|
||
});
|
||
|
||
} else {
|
||
// ===== INPUT BARU =====
|
||
|
||
if (jenis.value === "EVENT") {
|
||
|
||
db.push({
|
||
id: crypto.randomUUID(),
|
||
tanggal: tanggalInput.value,
|
||
shift: shiftInput.value,
|
||
kategori: "Event",
|
||
jenis: "EVENT",
|
||
namaEvent: namaEvent.value,
|
||
penyelenggara: penyelenggaraEvent.value || "-",
|
||
lokasiEvent: lokasiEvent.value,
|
||
wilayah: wilayah.value || "-",
|
||
jumlahPasien: jumlahPasien.value
|
||
? parseInt(jumlahPasien.value)
|
||
: 0,
|
||
keterangan: catatan.value || "-"
|
||
});
|
||
|
||
} else {
|
||
|
||
let kategori;
|
||
if (jenis.value === "BATAL") {
|
||
kategori = "Batal";
|
||
} else if (["AIRS","AIDA","MSAD"].includes(jenis.value)) {
|
||
kategori = "Aktivitas";
|
||
} else {
|
||
kategori = "Pelayanan";
|
||
}
|
||
|
||
|
||
db.push({
|
||
id: crypto.randomUUID(),
|
||
tanggal: tanggalInput.value,
|
||
shift: shiftInput.value,
|
||
kategori,
|
||
jenis: jenis.value,
|
||
emergency: emergency.value || "-",
|
||
wilayah: wilayah.value,
|
||
tujuan: tujuan.value || "-",
|
||
derajat: derajat.value || "-",
|
||
status: hitungStatus(jenis.value),
|
||
catatan: catatan.value
|
||
});
|
||
}
|
||
}
|
||
|
||
localStorage.setItem("ambulans2026", JSON.stringify(db));
|
||
|
||
modal.hide();
|
||
tampil();
|
||
normalisasiDataAmbulans();
|
||
modalMode = "input";
|
||
document.getElementById("btnHapusKasus").classList.add("d-none");
|
||
delete document.getElementById("modalInput").dataset.editId;
|
||
updatePetugasList();
|
||
|
||
}
|
||
|
||
|
||
const SHIFT_LIST = ["Pagi","Sore","Malam"];
|
||
|
||
function tampil(){
|
||
|
||
const tgl = tanggalInput.value;
|
||
let db = JSON.parse(localStorage.getItem("ambulans2026") || "[]");
|
||
db = db.filter(d => d.tanggal === tgl);
|
||
|
||
let html = "";
|
||
|
||
SHIFT_LIST.forEach(shiftNama => {
|
||
|
||
// ===== META PETUGAS =====
|
||
let meta = JSON.parse(
|
||
localStorage.getItem("shiftMeta_" + tgl + "_" + shiftNama)
|
||
) || {
|
||
kepalaJaga: "-",
|
||
perawat: [],
|
||
operator: []
|
||
};
|
||
|
||
// ===== DATA KASUS SHIFT =====
|
||
let detailAll = db.filter(d => d.shift === shiftNama);
|
||
|
||
let detail = detailAll.filter(d => d.kategori !== "Event");
|
||
|
||
let eventList = detailAll.filter(d => d.kategori === "Event");
|
||
|
||
|
||
let pelayanan = detail.filter(d => d.kategori === "Pelayanan").length;
|
||
let aktivitas = detail.filter(d => d.kategori === "Aktivitas").length;
|
||
let batal = detail.filter(d => d.kategori === "Batal").length;
|
||
let standby = eventList.length;
|
||
let trueEmergency = detail.filter(d => d.emergency === "true").length;
|
||
let nonEmergency = detail.filter(d => d.emergency === "false").length;
|
||
|
||
let nonEvent = detail.filter(d => d.kategori !== "Event");
|
||
|
||
|
||
// ===== HEADER SHIFT =====
|
||
html += `
|
||
<tr class="table-secondary">
|
||
<td colspan="8" class="fw-bold">
|
||
📅 ${tgl} — SHIFT ${shiftNama.toUpperCase()}
|
||
|
||
<div class="float-end d-flex gap-1">
|
||
|
||
<button class="btn btn-sm btn-outline-dark"
|
||
onclick="openModalArmada('${tgl}','${shiftNama}')">
|
||
🚑 Armada
|
||
</button>
|
||
|
||
<button class="btn btn-sm btn-outline-primary"
|
||
onclick="openModalPetugas('${shiftNama}')">
|
||
✏️ Petugas
|
||
</button>
|
||
|
||
<button class="btn btn-sm btn-outline-success"
|
||
onclick="editShift('${tgl}','${shiftNama}')">
|
||
✏️ Kegiatan
|
||
</button>
|
||
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
`;
|
||
|
||
// ===== PETUGAS =====
|
||
html += `
|
||
<tr class="table-light">
|
||
<td colspan="8">
|
||
<div class="row g-2">
|
||
<!-- ================= KOLOM 1 ================= -->
|
||
<div class="col-md-4">
|
||
👤 <strong>Kepala Jaga:</strong><br>
|
||
${meta.kepalaJaga || "-"}
|
||
</div>
|
||
<!-- ================= KOLOM 2 ================= -->
|
||
<div class="col-md-4">
|
||
🧑⚕️ <strong>Perawat:</strong><br>
|
||
${meta.perawat.join(", ") || "-"}
|
||
</div>
|
||
<!-- ================= KOLOM 3 ================= -->
|
||
<div class="col-md-4">
|
||
🎧 <strong>Operator:</strong><br>
|
||
${meta.operator.join(", ") || "-"}
|
||
</div>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
`;
|
||
|
||
// ===== RESUME SHIFT =====
|
||
html += `
|
||
<tr class="table-light">
|
||
<td colspan="8">
|
||
<!-- ================= JUDUL ================= -->
|
||
<div class="fw-bold mb-2">
|
||
📝 Resume Shift
|
||
</div>
|
||
<!-- ================= ISI ================= -->
|
||
<div class="row g-2">
|
||
<!-- ================= KOLOM 1 ================= -->
|
||
<div class="col-md-4">
|
||
🚑 Pelayanan Dengan Ambulans 119:
|
||
<strong>${pelayanan}</strong><br>
|
||
🧑⚕️ Aktivitas Tanpa Ambulans:
|
||
<strong>${aktivitas}</strong>
|
||
</div>
|
||
<!-- ================= KOLOM 2 ================= -->
|
||
<div class="col-md-4">
|
||
❌ Tidak Terlayani:
|
||
<strong>${batal}</strong><br>
|
||
📌 Standby Event:
|
||
<strong>${standby}</strong>
|
||
</div>
|
||
<!-- ================= KOLOM 3 ================= -->
|
||
<div class="col-md-4">
|
||
🔴 True Emergency:
|
||
<strong>${trueEmergency}</strong><br>
|
||
🟢 Non Emergency:
|
||
<strong>${nonEmergency}</strong>
|
||
</div>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
`;
|
||
|
||
// ===== TABEL DETAIL KASUS =====
|
||
detail.sort((a,b)=>{
|
||
if(a.tanggal !== b.tanggal){
|
||
return a.tanggal.localeCompare(b.tanggal);
|
||
}
|
||
return a.shift.localeCompare(b.shift);
|
||
});
|
||
|
||
if(detail.length){
|
||
html += `
|
||
<tr class="table-light text-center fw-bold">
|
||
<td>Kategori</td>
|
||
<td>Jenis Layanan</td>
|
||
<td>Status Kegawatan</td>
|
||
<td>Wilayah</td>
|
||
<td>Tujuan</td>
|
||
<td class="dashboard-derajat">Derajat Transfer</td>
|
||
<td>Status</td>
|
||
<td>Catatan</td>
|
||
</tr>
|
||
`;
|
||
|
||
detail.forEach(d=>{
|
||
html += `
|
||
<tr>
|
||
<td>${d.kategori}</td>
|
||
<td><strong>${JENIS_LABEL[d.jenis]}</strong></td>
|
||
<td class="text-center">${
|
||
d.emergency === "true"
|
||
? '<span class="badge bg-danger">True Emergency</span>'
|
||
: d.emergency === "false"
|
||
? '<span class="badge bg-success">Non Emergency</span>'
|
||
: '-'
|
||
}</td>
|
||
<td>${d.wilayah}</td>
|
||
<td>${d.tujuan}</td>
|
||
<td class="dashboard-derajat">${d.derajat}</td>
|
||
<td>${d.status || "-"}</td>
|
||
<td>${d.catatan || ""}</td>
|
||
</tr>
|
||
`;
|
||
});
|
||
} else {
|
||
html += `
|
||
<tr>
|
||
<td colspan="8" class="text-center text-muted">
|
||
Tidak terdapat permintaan pelayanan Ambulans 119 pada shift ini.
|
||
Petugas dalam kondisi siap siaga.
|
||
</td>
|
||
</tr>
|
||
`;
|
||
}
|
||
|
||
// ===== SIAGA EVENT =====
|
||
if(eventList.length){
|
||
html += `
|
||
<tr class="table-warning text-center fw-bold">
|
||
<td style="width:10%">Kategori</td>
|
||
<td style="width:25%">Nama Event</td>
|
||
<td style="width:15%">Penyelenggara</td>
|
||
<td style="width:20%">Lokasi Event</td>
|
||
<td style="width:10%">Wilayah</td>
|
||
<td style="width:10%">Jumlah Pasien</td>
|
||
<td style="width:10%">Keterangan</td>
|
||
<td style="width:130px">Aksi</td>
|
||
|
||
</tr>
|
||
`;
|
||
|
||
eventList.forEach(e=>{
|
||
html += `
|
||
<tr>
|
||
<td>Event</td>
|
||
<td>${safe(e.namaEvent)}</td>
|
||
<td>${safe(e.penyelenggara)}</td>
|
||
<td>${safe(e.lokasiEvent)}</td>
|
||
<td>${safe(e.wilayah)}</td>
|
||
<td class="text-center">
|
||
${e.jumlahPasien > 0 ? e.jumlahPasien + " Pasien" : "-"}
|
||
</td>
|
||
<td>${safe(e.keterangan)}</td>
|
||
<td class="text-center">
|
||
<button class="btn btn-sm btn-outline-primary"
|
||
onclick="editEvent('${e.id}')">✏️</button>
|
||
<button class="btn btn-sm btn-outline-danger"
|
||
onclick="hapusEvent('${e.id}')">🗑️</button>
|
||
</td>
|
||
</tr>
|
||
`;
|
||
});
|
||
}
|
||
|
||
|
||
// ===== ARMADA =====
|
||
let armada = JSON.parse(
|
||
localStorage.getItem("armada_" + tgl + "_" + shiftNama) || "{}"
|
||
);
|
||
function barBBM(val){
|
||
|
||
val = parseFloat(val || 0);
|
||
|
||
const max = 4;
|
||
|
||
// clamp
|
||
val = Math.max(0, Math.min(val, max));
|
||
|
||
// ===== SAME LOGIC AS updateFuelSemi =====
|
||
let angle = 180 - (val * 45);
|
||
let rad = angle * Math.PI / 180;
|
||
|
||
const cx = 100;
|
||
const cy = 110;
|
||
const length = 80;
|
||
|
||
const x = cx + length * Math.cos(rad);
|
||
const y = cy - length * Math.sin(rad);
|
||
|
||
// ===== COLOR SYSTEM =====
|
||
let color = "#2ecc71";
|
||
if(val <= 1) color = "#e74c3c";
|
||
else if(val <= 2.5) color = "#f1c40f";
|
||
|
||
return `
|
||
<div class="bbm-gauge">
|
||
|
||
<svg viewBox="0 0 200 120" width="180">
|
||
|
||
<!-- ARC BACKGROUND -->
|
||
<path d="M10 110 A90 90 0 0 1 190 110"
|
||
stroke="#2b2b2b"
|
||
stroke-width="12"
|
||
fill="none"
|
||
stroke-linecap="round"/>
|
||
|
||
<!-- NEEDLE -->
|
||
<line
|
||
x1="${cx}" y1="${cy}"
|
||
x2="${x}" y2="${y}"
|
||
stroke="${color}"
|
||
stroke-width="5"
|
||
stroke-linecap="round"
|
||
/>
|
||
|
||
<!-- CENTER CAP -->
|
||
<circle cx="${cx}" cy="${cy}" r="7" fill="#111" stroke="#444" stroke-width="2"/>
|
||
|
||
</svg>
|
||
|
||
<!-- VALUE -->
|
||
<div style="text-align:center;font-weight:bold;color:${color};margin-top:2px;">
|
||
BBM ${val.toFixed(1)} / 4
|
||
</div>
|
||
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
// ================= TABUNG OKSIGEN =================
|
||
function oxyBar(val,label=""){
|
||
|
||
val = parseInt(val || 0);
|
||
|
||
// BATAS NILAI
|
||
if(val < 0) val = 0;
|
||
if(val > 150) val = 150;
|
||
|
||
// PERSENTASE TABUNG
|
||
let persen = (val / 150) * 100;
|
||
|
||
// ================= STATUS =================
|
||
let warna = "oxy-green";
|
||
let status = "FULL";
|
||
let bg = "linear-gradient(to top,#16a085,#2ecc71)";
|
||
let critical = "";
|
||
|
||
// 0-20 = HABIS
|
||
if(val <= 20){
|
||
|
||
warna = "oxy-gray";
|
||
status = "HABIS";
|
||
|
||
bg = "linear-gradient(to top,#6c757d,#adb5bd)";
|
||
|
||
critical = "oxy-critical";
|
||
}
|
||
|
||
// 21-40 = LOW
|
||
else if(val <= 40){
|
||
|
||
warna = "oxy-red";
|
||
status = "LOW";
|
||
|
||
bg = "linear-gradient(to top,#c0392b,#ff7675)";
|
||
|
||
critical = "oxy-critical";
|
||
}
|
||
|
||
// 41-80 = MID
|
||
else if(val <= 80){
|
||
|
||
warna = "oxy-yellow";
|
||
status = "MID";
|
||
|
||
bg = "linear-gradient(to top,#f39c12,#ffe066)";
|
||
}
|
||
|
||
// 81-150 = FULL
|
||
else{
|
||
|
||
warna = "oxy-green";
|
||
status = "FULL";
|
||
|
||
bg = "linear-gradient(to top,#16a085,#2ecc71)";
|
||
}
|
||
|
||
// ================= RENDER =================
|
||
return `
|
||
<div class="oxy-item">
|
||
<div class="oxy-head"></div>
|
||
<div class="oxy-cylinder ${critical}">
|
||
<div
|
||
class="oxy-fill"
|
||
style="
|
||
height:${persen}%;
|
||
background:${bg};
|
||
">
|
||
</div>
|
||
</div>
|
||
<div class="oxy-label">
|
||
${val} Kgf/cm³
|
||
</div>
|
||
<div class="oxy-status ${warna}">
|
||
${status}
|
||
</div>
|
||
<small>${label}</small>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
html += `
|
||
<tr class="table-light">
|
||
<td colspan="8">
|
||
|
||
<div class="fw-bold mb-3 fs-6">
|
||
🚑 Status Armada Ambulans
|
||
</div>
|
||
|
||
<div class="row g-3">
|
||
|
||
<!-- ================= AE 1 ================= -->
|
||
<div class="col-md-4">
|
||
<div class="armada-box">
|
||
|
||
<div class="fw-bold fs-6 mb-2">
|
||
AE 1 - Premio ${badgeStatus(armada.status_1)}
|
||
</div>
|
||
|
||
<!-- BBM -->
|
||
<div>
|
||
<small class="text-muted fw-bold">⛽ BBM</small>
|
||
${barBBM(parseFloat(armada.bbm_1 || 0))}
|
||
</div>
|
||
|
||
<!-- KM -->
|
||
<div class="armada-info mt-2">
|
||
📍 Kilometer (km) :
|
||
<strong>${armada.km_1 || "-"}</strong>
|
||
</div>
|
||
|
||
<!-- OXYGEN -->
|
||
<div class="mt-3">
|
||
<small class="text-muted fw-bold">🫁 Oksigen</small>
|
||
<div class="oxy-group">
|
||
${oxyBar(armada.oxy1_1,"Tabung 1")}
|
||
${oxyBar(armada.oxy1_2,"Tabung 2")}
|
||
${oxyBar(armada.oxy1_3,"O2 Transport")}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- CADANGAN -->
|
||
<div class="armada-info mt-3">
|
||
🧯 Tabung Oksigen Cadangan :
|
||
${
|
||
armada.oxy1_cadangan === "tersegel"
|
||
? `<span class="oxy-badge-tersedia">🟢 Tersegel</span>`
|
||
: `<span class="oxy-badge-kosong">🔴 Kosong</span>`
|
||
}
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ================= AE 2 ================= -->
|
||
<div class="col-md-4">
|
||
<div class="armada-box">
|
||
|
||
<div class="fw-bold fs-6 mb-2">
|
||
AE 2 - Commuter ${badgeStatus(armada.status_2)}
|
||
</div>
|
||
|
||
<!-- BBM -->
|
||
<div>
|
||
<small class="text-muted fw-bold">⛽ BBM</small>
|
||
${barBBM(parseFloat(armada.bbm_2 || 0))}
|
||
</div>
|
||
|
||
<!-- KM -->
|
||
<div class="armada-info mt-2">
|
||
📍 Kilometer (km) :
|
||
<strong>${armada.km_2 || "-"}</strong>
|
||
</div>
|
||
|
||
<!-- OXYGEN -->
|
||
<div class="mt-3">
|
||
<small class="text-muted fw-bold">🫁 Oksigen</small>
|
||
<div class="oxy-group">
|
||
${oxyBar(armada.oxy2_1,"Tabung 1")}
|
||
${oxyBar(armada.oxy2_2,"Tabung 2")}
|
||
${oxyBar(armada.oxy2_3,"O2 Transport")}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- CADANGAN -->
|
||
<div class="armada-info mt-3">
|
||
🧯 Tabung Oksigen Cadangan :
|
||
${
|
||
armada.oxy2_cadangan === "tersegel"
|
||
? `<span class="oxy-badge-tersedia">🟢 Tersegel</span>`
|
||
: `<span class="oxy-badge-kosong">🔴 Kosong</span>`
|
||
}
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ================= AE 3 ================= -->
|
||
<div class="col-md-4">
|
||
<div class="armada-box">
|
||
|
||
<div class="fw-bold fs-6 mb-2">
|
||
AE 3 - ELF ${badgeStatus(armada.status_3)}
|
||
</div>
|
||
|
||
<!-- BBM -->
|
||
<div>
|
||
<small class="text-muted fw-bold">⛽ BBM</small>
|
||
${barBBM(parseFloat(armada.bbm_3 || 0))}
|
||
</div>
|
||
|
||
<!-- KM -->
|
||
<div class="armada-info mt-2">
|
||
📍 Kilometer (km) :
|
||
<strong>${armada.km_3 || "-"}</strong>
|
||
</div>
|
||
|
||
<!-- OXYGEN -->
|
||
<div class="mt-3">
|
||
<small class="text-muted fw-bold">🫁 Oksigen</small>
|
||
<div class="oxy-group">
|
||
${oxyBar(armada.oxy3_1,"Tabung 1")}
|
||
${oxyBar(armada.oxy3_2,"Tabung 2")}
|
||
${oxyBar(armada.oxy3_3,"O2 Transport")}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- CADANGAN -->
|
||
<div class="armada-info mt-3">
|
||
🧯 Tabung Oksigen Cadangan :
|
||
${
|
||
armada.oxy3_cadangan === "tersegel"
|
||
? `<span class="oxy-badge-tersedia">🟢 Tersegel</span>`
|
||
: `<span class="oxy-badge-kosong">🔴 Kosong</span>`
|
||
}
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</td>
|
||
</tr>
|
||
`;
|
||
|
||
// ===== KENDALA SHIFT =====
|
||
html += `
|
||
<tr class="table-light">
|
||
<td colspan="8">
|
||
<label class="fw-bold">📝 Catatan Harian / Kendala Operasional</label>
|
||
<textarea class="form-control form-control-sm mb-2"
|
||
id="kendala_${tgl}_${shiftNama}"
|
||
placeholder="Tuliskan kendala operasional shift ini..."></textarea>
|
||
|
||
<button class="btn btn-sm btn-outline-warning"
|
||
onclick="simpanKendalaShift('${tgl}','${shiftNama}')">
|
||
Simpan Kendala
|
||
</button>
|
||
|
||
<div id="listKendala_${tgl}_${shiftNama}" class="mt-2"></div>
|
||
</td>
|
||
</tr>
|
||
`;
|
||
// render kendala async
|
||
setTimeout(()=>{
|
||
renderKendalaShift(tgl, shiftNama);
|
||
},0);
|
||
|
||
});
|
||
|
||
tabelData.innerHTML = html;
|
||
}
|
||
|
||
tampil();
|
||
tanggalInput.addEventListener("change", tampil);
|
||
|
||
function tambahPerawat(val = ""){
|
||
perawatContainer.insertAdjacentHTML("beforeend",
|
||
`<input type="text" class="form-control mb-1 perawat"
|
||
list="listPetugas" value="${val}">`
|
||
);
|
||
}
|
||
|
||
function tambahOperator(val = ""){
|
||
operatorContainer.insertAdjacentHTML("beforeend",
|
||
`<input type="text" class="form-control mb-1 operator"
|
||
list="listPetugas" value="${val}">`
|
||
);
|
||
}
|
||
|
||
function updatePetugasList(){
|
||
const namaSet = new Set();
|
||
|
||
// dari shiftMeta
|
||
Object.keys(localStorage).forEach(key=>{
|
||
if(!key.startsWith("shiftMeta_")) return;
|
||
const m = JSON.parse(localStorage.getItem(key));
|
||
if(!m) return;
|
||
|
||
if(m.kepalaJaga && m.kepalaJaga !== "-")
|
||
namaSet.add(m.kepalaJaga);
|
||
|
||
(m.perawat||[]).forEach(n=>n && namaSet.add(n));
|
||
(m.operator||[]).forEach(n=>n && namaSet.add(n));
|
||
});
|
||
|
||
// dari input langsung
|
||
(JSON.parse(localStorage.getItem("tempPetugas")||"[]"))
|
||
.forEach(n=>namaSet.add(n));
|
||
|
||
const dl = document.getElementById("listPetugas");
|
||
if(!dl) return;
|
||
|
||
dl.innerHTML = "";
|
||
[...namaSet].sort().forEach(n=>{
|
||
dl.insertAdjacentHTML("beforeend", `<option value="${n}">`);
|
||
});
|
||
}
|
||
|
||
|
||
function simpanKendalaShift(tgl, shift){
|
||
|
||
let textarea = document.getElementById(
|
||
`kendala_${tgl}_${shift}`
|
||
);
|
||
|
||
if(!textarea.value.trim()) return;
|
||
|
||
let db = JSON.parse(
|
||
localStorage.getItem("kendalaShift") || "[]"
|
||
);
|
||
|
||
db.push({
|
||
id: crypto.randomUUID(), // ✅ ID UNIK
|
||
tanggal: tgl,
|
||
shift: shift,
|
||
isi: textarea.value.trim()
|
||
});
|
||
|
||
localStorage.setItem(
|
||
"kendalaShift",
|
||
JSON.stringify(db)
|
||
);
|
||
|
||
textarea.value = "";
|
||
renderKendalaShift(tgl, shift);
|
||
}
|
||
|
||
|
||
function renderKendalaShift(tgl, shift){
|
||
|
||
let db = JSON.parse(
|
||
localStorage.getItem("kendalaShift") || "[]"
|
||
);
|
||
|
||
let data = db.filter(d =>
|
||
d.tanggal === tgl &&
|
||
d.shift === shift
|
||
);
|
||
|
||
let container = document.getElementById(
|
||
`listKendala_${tgl}_${shift}`
|
||
);
|
||
|
||
if(!container) return;
|
||
|
||
if(data.length === 0){
|
||
container.innerHTML = "";
|
||
return;
|
||
}
|
||
|
||
let html = `
|
||
<div class="alert alert-warning py-2 mb-0">
|
||
<ul class="mb-0">
|
||
`;
|
||
|
||
data.forEach(d=>{
|
||
html += `
|
||
<li class="d-flex justify-content-between align-items-start">
|
||
<span>${d.isi}</span>
|
||
<div class="btn-group btn-group-sm">
|
||
<button class="btn btn-outline-dark"
|
||
onclick="editKendala('${d.id}')">✏️</button>
|
||
<button class="btn btn-outline-danger"
|
||
onclick="hapusKendala('${d.id}')">🗑️</button>
|
||
</div>
|
||
</li>
|
||
`;
|
||
});
|
||
|
||
html += `
|
||
</ul>
|
||
</div>
|
||
`;
|
||
|
||
container.innerHTML = html;
|
||
}
|
||
|
||
function editShift(tanggal, shift){
|
||
modalMode = "editShift";
|
||
|
||
const body = document.querySelector("#modalInput .modal-body");
|
||
const title = document.querySelector("#modalInput .modal-title");
|
||
|
||
title.innerText = "✏️ Edit / Hapus Kegiatan Shift " + shift;
|
||
|
||
let db = JSON.parse(localStorage.getItem("ambulans2026") || "[]");
|
||
let list = db.filter(d =>
|
||
d.tanggal === tanggal &&
|
||
d.shift === shift &&
|
||
d.kategori !== "Event"
|
||
);
|
||
|
||
|
||
if(!list.length){
|
||
body.innerHTML = `<div class="text-muted">Tidak ada data</div>`;
|
||
modal.show();
|
||
return;
|
||
}
|
||
|
||
let html = `<div class="list-group">`;
|
||
|
||
list.forEach(d=>{
|
||
html += `
|
||
<div class="list-group-item d-flex justify-content-between align-items-start">
|
||
<div>
|
||
<strong>${JENIS_LABEL[d.jenis]}</strong><br>
|
||
<small>${d.tujuan}</small>
|
||
</div>
|
||
<div class="btn-group btn-group-sm">
|
||
<button class="btn btn-outline-primary"
|
||
onclick="konfirmasiEdit('${d.id}')"
|
||
title="Edit Data">
|
||
✏️
|
||
</button>
|
||
|
||
<button class="btn btn-outline-danger"
|
||
onclick="konfirmasiHapus('${d.id}')"
|
||
title="Hapus Data">
|
||
✖
|
||
</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
});
|
||
|
||
html += `</div>`;
|
||
body.innerHTML = html;
|
||
|
||
modal.show();
|
||
}
|
||
|
||
|
||
|
||
function openModalInput(shift){
|
||
modalMode = "input";
|
||
openModal(shift);
|
||
}
|
||
|
||
function openModalPetugas(shift){
|
||
modalMode = "petugas";
|
||
openModal(shift);
|
||
}
|
||
|
||
function editKasus(id){
|
||
document.getElementById("btnHapusKasus").classList.remove("d-none");
|
||
|
||
let db = JSON.parse(localStorage.getItem("ambulans2026") || "[]");
|
||
let d = db.find(x => x.id === id);
|
||
|
||
if(!d){
|
||
alert("Data tidak ditemukan");
|
||
return;
|
||
}
|
||
|
||
modalMode = "editKasus";
|
||
document.getElementById("modalInput").dataset.editId = id;
|
||
restoreModalForm();
|
||
document.querySelector("#modalInput .modal-title")
|
||
.innerText = "✏️ Edit Kegiatan";
|
||
|
||
openModal(d.shift);
|
||
|
||
jenis.value = d.jenis;
|
||
onJenisChange();
|
||
emergency.value = d.emergency !== "-" ? d.emergency : "";
|
||
wilayah.value = d.wilayah;
|
||
tujuan.value = d.tujuan !== "-" ? d.tujuan : "";
|
||
derajat.value = d.derajat !== "-" ? d.derajat : "";
|
||
catatan.value = d.catatan || "";
|
||
}
|
||
|
||
function hapusKasus(id){
|
||
if(!confirm("Hapus data kegiatan ini?")) return;
|
||
|
||
let db = JSON.parse(localStorage.getItem("ambulans2026") || "[]");
|
||
db = db.filter(d => d.id !== id);
|
||
|
||
localStorage.setItem("ambulans2026", JSON.stringify(db));
|
||
|
||
modal.hide();
|
||
tampil();
|
||
}
|
||
|
||
function konfirmasiEdit(id){
|
||
if(confirm("Edit data kegiatan ini?")){
|
||
editKasus(id);
|
||
}
|
||
}
|
||
|
||
function konfirmasiHapus(id){
|
||
if(confirm("Hapus data kegiatan ini?")){
|
||
hapusKasus(id);
|
||
}
|
||
}
|
||
|
||
document.addEventListener("input", e => {
|
||
if(!e.target.matches("#kepalaJaga,.perawat,.operator")) return;
|
||
|
||
let nama = e.target.value.trim();
|
||
if(!nama) return;
|
||
|
||
let temp = JSON.parse(localStorage.getItem("tempPetugas") || "[]");
|
||
if(!temp.includes(nama)){
|
||
temp.push(nama);
|
||
localStorage.setItem("tempPetugas", JSON.stringify(temp));
|
||
}
|
||
});
|
||
function showTab(tab){
|
||
["mingguan","harian","bulanan"].forEach(t=>{
|
||
document.getElementById("tab"+t.charAt(0).toUpperCase()+t.slice(1))
|
||
?.classList.add("d-none");
|
||
});
|
||
|
||
document.getElementById("tab"+tab.charAt(0).toUpperCase()+tab.slice(1))
|
||
?.classList.remove("d-none");
|
||
|
||
document.querySelectorAll(".nav-link")
|
||
.forEach(b=>b.classList.remove("active"));
|
||
|
||
event.target.classList.add("active");
|
||
}
|
||
|
||
|
||
function buatTabelHarianHTML(db){
|
||
|
||
let html = "";
|
||
const dataByTanggalShift = {};
|
||
|
||
|
||
db.forEach(d => {
|
||
const key = d.tanggal + "_" + d.shift;
|
||
if(!dataByTanggalShift[key]) dataByTanggalShift[key] = [];
|
||
dataByTanggalShift[key].push(d);
|
||
});
|
||
|
||
Object.keys(dataByTanggalShift).forEach(key => {
|
||
|
||
const [tgl, shiftNama] = key.split("_");
|
||
const detail = dataByTanggalShift[key];
|
||
|
||
html += `
|
||
<tr class="table-secondary">
|
||
<td colspan="8" class="fw-bold">
|
||
📅 ${tgl} — SHIFT ${shiftNama.toUpperCase()}
|
||
</td>
|
||
</tr>
|
||
`;
|
||
|
||
html += `
|
||
<tr class="table-light text-center fw-bold">
|
||
<td>Kategori</td>
|
||
<td>Jenis Layanan</td>
|
||
<td>Status Kegawatan</td>
|
||
<td>Wilayah</td>
|
||
<td>Tujuan</td>
|
||
<td class="dashboard-derajat">Derajat</td>
|
||
<td>Status</td>
|
||
<td>Catatan</td>
|
||
</tr>
|
||
`;
|
||
|
||
detail.forEach(d => {
|
||
html += `
|
||
<tr>
|
||
<td>${d.kategori}</td>
|
||
<td><strong>${JENIS_LABEL[d.jenis]}</strong></td>
|
||
<td class="text-center">${
|
||
d.emergency === "true"
|
||
? '<span class="badge bg-danger">True</span>'
|
||
: d.emergency === "false"
|
||
? '<span class="badge bg-success">Non</span>'
|
||
: '-'
|
||
}</td>
|
||
<td>${safe(d.wilayah)}</td>
|
||
<td>${safe(d.tujuan)}</td>
|
||
<td class="dashboard-derajat">${safe(d.derajat)}</td>
|
||
<td>${safe(d.status)}</td>
|
||
<td>${safe(d.catatan)}</td>
|
||
</tr>
|
||
`;
|
||
});
|
||
|
||
});
|
||
|
||
return `
|
||
<div class="table-responsive">
|
||
<table class="table table-sm table-report align-middle">
|
||
<tbody>${html}</tbody>
|
||
</table>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
function editKendala(id){
|
||
|
||
let db = JSON.parse(
|
||
localStorage.getItem("kendalaShift") || "[]"
|
||
);
|
||
|
||
const k = db.find(x => x.id === id);
|
||
if(!k){
|
||
alert("Data kendala tidak ditemukan");
|
||
return;
|
||
}
|
||
|
||
document.getElementById("kendalaId").value = k.id;
|
||
document.getElementById("kendalaTanggal").value = k.tanggal;
|
||
document.getElementById("kendalaShift").value = k.shift;
|
||
document.getElementById("kendalaIsi").value = k.isi;
|
||
|
||
const modalK = bootstrap.Modal.getOrCreateInstance(
|
||
document.getElementById("modalKendala")
|
||
);
|
||
modalK.show();
|
||
}
|
||
|
||
function simpanEditKendala(){
|
||
|
||
const id = document.getElementById("kendalaId").value;
|
||
|
||
let db = JSON.parse(
|
||
localStorage.getItem("kendalaShift") || "[]"
|
||
);
|
||
|
||
const idx = db.findIndex(x => x.id === id);
|
||
if(idx === -1){
|
||
alert("Data tidak ditemukan");
|
||
return;
|
||
}
|
||
|
||
db[idx].tanggal =
|
||
document.getElementById("kendalaTanggal").value;
|
||
|
||
db[idx].shift =
|
||
document.getElementById("kendalaShift").value;
|
||
|
||
db[idx].isi =
|
||
document.getElementById("kendalaIsi").value;
|
||
|
||
localStorage.setItem(
|
||
"kendalaShift",
|
||
JSON.stringify(db)
|
||
);
|
||
|
||
bootstrap.Modal.getInstance(
|
||
document.getElementById("modalKendala")
|
||
).hide();
|
||
|
||
tampil(); // refresh harian & mingguan
|
||
}
|
||
|
||
function hapusKendala(id){
|
||
|
||
if(!confirm("Hapus kendala operasional ini?")) return;
|
||
|
||
let db = JSON.parse(
|
||
localStorage.getItem("kendalaShift") || "[]"
|
||
);
|
||
|
||
db = db.filter(k => k.id !== id);
|
||
|
||
localStorage.setItem(
|
||
"kendalaShift",
|
||
JSON.stringify(db)
|
||
);
|
||
|
||
tampil(); // refresh harian + mingguan
|
||
}
|
||
|
||
function normalisasiDataAmbulans(){
|
||
let db = JSON.parse(localStorage.getItem("ambulans2026") || "[]");
|
||
let berubah = false;
|
||
|
||
db = db.map(d => {
|
||
|
||
if (d.jenis === "EVENT") {
|
||
return d;
|
||
}
|
||
|
||
let kategoriBaru;
|
||
if (d.jenis === "BATAL") {
|
||
kategoriBaru = "Batal";
|
||
} else if (["AIRS","AIDA","MSAD"].includes(d.jenis)) {
|
||
kategoriBaru = "Aktivitas";
|
||
} else {
|
||
kategoriBaru = "Pelayanan";
|
||
}
|
||
|
||
let emergencyBaru = d.emergency;
|
||
if (d.jenis === "BATAL") emergencyBaru = "false";
|
||
|
||
if (
|
||
d.kategori !== kategoriBaru ||
|
||
d.emergency !== emergencyBaru
|
||
) {
|
||
berubah = true;
|
||
return {
|
||
...d,
|
||
kategori: kategoriBaru,
|
||
emergency: emergencyBaru
|
||
};
|
||
}
|
||
|
||
return d;
|
||
});
|
||
|
||
|
||
if (berubah) {
|
||
localStorage.setItem("ambulans2026", JSON.stringify(db));
|
||
console.log("✅ Normalisasi data ambulans diterapkan");
|
||
}
|
||
}
|
||
|
||
function restoreModalForm(){
|
||
document.querySelector("#modalInput .modal-body").innerHTML = modalFormHTML;
|
||
|
||
["namaEvent","penyelenggaraEvent","lokasiEvent","jumlahPasien"]
|
||
.forEach(id=>{
|
||
const el = document.getElementById(id);
|
||
if(el) el.value = "";
|
||
});
|
||
|
||
document.querySelector("#modalInput .modal-title")
|
||
.innerText = "Input Laporan Shift";
|
||
}
|
||
|
||
function editEvent(id){
|
||
let db = JSON.parse(localStorage.getItem("ambulans2026") || "[]");
|
||
const e = db.find(x => x.id === id && x.jenis === "EVENT");
|
||
|
||
if(!e){
|
||
alert("Data event tidak ditemukan");
|
||
return;
|
||
}
|
||
|
||
modalMode = "editKasus";
|
||
document.getElementById("modalInput").dataset.editId = id;
|
||
|
||
restoreModalForm();
|
||
document.querySelector("#modalInput .modal-title")
|
||
.innerText = "✏️ Edit Standby Event";
|
||
|
||
openModal(e.shift);
|
||
|
||
jenis.value = "EVENT";
|
||
onJenisChange();
|
||
|
||
namaEvent.value = e.namaEvent;
|
||
penyelenggaraEvent.value = e.penyelenggara !== "-" ? e.penyelenggara : "";
|
||
lokasiEvent.value = e.lokasiEvent;
|
||
wilayah.value = e.wilayah !== "-" ? e.wilayah : "";
|
||
jumlahPasien.value = e.jumlahPasien > 0 ? e.jumlahPasien : "";
|
||
catatan.value = e.keterangan !== "-" ? e.keterangan : "";
|
||
}
|
||
|
||
function hapusEvent(id){
|
||
if(!confirm("Hapus data Standby Event ini?")) return;
|
||
|
||
let db = JSON.parse(localStorage.getItem("ambulans2026") || "[]");
|
||
db = db.filter(d => d.id !== id);
|
||
|
||
localStorage.setItem("ambulans2026", JSON.stringify(db));
|
||
|
||
tampil(); // refresh harian
|
||
}
|
||
|
||
function animateShiftCard(el){
|
||
// reset semua card
|
||
document.querySelectorAll('.card-shift')
|
||
.forEach(c=>c.classList.remove('shift-active'));
|
||
|
||
// trigger animasi
|
||
el.classList.add('shift-active');
|
||
}
|
||
|
||
function formatTanggalID(dateStr){
|
||
const d = new Date(dateStr);
|
||
return d.toLocaleDateString("id-ID", {
|
||
day: "2-digit",
|
||
month: "long",
|
||
year: "numeric"
|
||
});
|
||
}
|
||
|
||
function tampilkanRekapMingguan(){
|
||
|
||
const tanggalVal = filterMinggu.value;
|
||
if(!tanggalVal){
|
||
alert("Pilih tanggal Selasa terlebih dahulu");
|
||
return;
|
||
}
|
||
|
||
const start = new Date(tanggalVal);
|
||
|
||
// 🔒 VALIDASI HARUS SELASA
|
||
if(start.getDay() !== 2){
|
||
rekapMingguan.innerHTML = `
|
||
<div class="alert alert-info">
|
||
ℹ️ Silakan pilih <strong>hari Selasa</strong>.<br>
|
||
Rekap mingguan dihitung dari <strong>Selasa ke Selasa</strong>.
|
||
</div>`;
|
||
return;
|
||
}
|
||
|
||
const end = new Date(start);
|
||
end.setDate(start.getDate() + 7); // Selasa → Selasa
|
||
|
||
const db = JSON.parse(localStorage.getItem("ambulans2026") || "[]");
|
||
|
||
const data = db.filter(d => {
|
||
if(!d.tanggal) return false;
|
||
const tgl = new Date(d.tanggal);
|
||
return tgl >= start && tgl < end;
|
||
});
|
||
|
||
if(!data.length){
|
||
rekapMingguan.innerHTML = `
|
||
<div class="alert alert-warning">
|
||
Tidak terdapat data laporan pada periode mingguan ini.
|
||
</div>`;
|
||
return;
|
||
}
|
||
|
||
/* =========================
|
||
KELOMPOK DATA
|
||
========================= */
|
||
const pelayanan = data.filter(d => d.kategori === "Pelayanan");
|
||
const aktivitas = data.filter(d => d.kategori === "Aktivitas");
|
||
const event = data.filter(d => d.kategori === "Event");
|
||
|
||
const tidakTerlayani = data.filter(d => d.kategori === "Batal");
|
||
|
||
|
||
const trueEmergensi = data.filter(d => d.emergency === "true");
|
||
const nonEmergensi = data.filter(d => d.emergency === "false");
|
||
|
||
/* =========================
|
||
NARASI MINGGUAN RESMI
|
||
========================= */
|
||
let html = `
|
||
<div class="alert alert-info">
|
||
<strong>Narasi Laporan Mingguan Ambulans 119</strong><br><br>
|
||
Berdasarkan rekapitulasi laporan Ambulans 119
|
||
pada periode <strong>${formatTanggalID(start)} s.d ${formatTanggalID(end)}</strong>,
|
||
tercatat sebanyak <strong>${data.length}</strong> kegiatan dengan rincian:
|
||
<ul class="mb-0">
|
||
<li>Pelayanan ambulans: <strong>${pelayanan.length}</strong> kegiatan</li>
|
||
<li>Aktivitas operasional/internal: <strong>${aktivitas.length}</strong> kegiatan</li>
|
||
<li>Tidak terlayani / batal: <strong>${tidakTerlayani.length}</strong> kegiatan</li>
|
||
<li>Standby event: <strong>${event.length}</strong> kegiatan</li>
|
||
<li>
|
||
Tercatat <strong>${trueEmergensi.length}</strong> kasus
|
||
<strong>True Emergency</strong> dan
|
||
<strong>${nonEmergensi.length}</strong> kasus
|
||
<strong>Non Emergency</strong>
|
||
</li>
|
||
</ul>
|
||
</div>`;
|
||
|
||
/* =========================
|
||
TABEL PELAYANAN & AKTIVITAS
|
||
========================= */
|
||
const utama = [...pelayanan, ...aktivitas];
|
||
|
||
if(utama.length){
|
||
html += `
|
||
<h6 class="mt-4">🚑 Tabel Pelayanan & Aktivitas</h6>
|
||
<table class="table table-sm table-report">
|
||
<thead class="table-light">
|
||
<tr>
|
||
<th>Tanggal</th>
|
||
<th>Shift</th>
|
||
<th>Kategori</th>
|
||
<th>Jenis Layanan</th>
|
||
<th>Status Kegawatan</th>
|
||
<th>Wilayah</th>
|
||
<th>Tujuan</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>`;
|
||
|
||
utama.forEach(d=>{
|
||
html += `
|
||
<tr>
|
||
<td>${formatTanggalID(d.tanggal)}</td>
|
||
<td>${d.shift || "-"}</td>
|
||
<td>${d.kategori}</td>
|
||
<td>${JENIS_LABEL[d.jenis] || d.jenis || "-"}</td>
|
||
<td class="text-center">
|
||
${
|
||
d.emergency === "true"
|
||
? '<span class="badge bg-danger">True Emergency</span>'
|
||
: d.emergency === "false"
|
||
? '<span class="badge bg-success">Non Emergency</span>'
|
||
: '-'
|
||
}
|
||
</td>
|
||
<td>${d.wilayah || "-"}</td>
|
||
<td>${d.tujuan || "-"}</td>
|
||
</tr>`;
|
||
});
|
||
|
||
html += `</tbody></table>`;
|
||
}
|
||
|
||
/* =========================
|
||
TABEL TIDAK TERLAYANI
|
||
========================= */
|
||
if(tidakTerlayani.length){
|
||
html += `
|
||
<h6 class="mt-4 text-danger">❌ Permintaan Tidak Terlayani</h6>
|
||
<table class="table table-sm table-report">
|
||
<thead class="table-danger">
|
||
<tr>
|
||
<th>Tanggal</th>
|
||
<th>Shift</th>
|
||
<th>Status Kegawatan</th>
|
||
<th>Wilayah</th>
|
||
<th>Tujuan</th>
|
||
<th>Catatan</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>`;
|
||
|
||
tidakTerlayani.forEach(d=>{
|
||
html += `
|
||
<tr>
|
||
<td>${formatTanggalID(d.tanggal)}</td>
|
||
<td>${d.shift || "-"}</td>
|
||
<td class="text-center">
|
||
${
|
||
d.emergency === "true"
|
||
? '<span class="badge bg-danger">True Emergency</span>'
|
||
: d.emergency === "false"
|
||
? '<span class="badge bg-success">Non Emergency</span>'
|
||
: '-'
|
||
}
|
||
</td>
|
||
<td>${d.wilayah || "-"}</td>
|
||
<td>${d.tujuan || "-"}</td>
|
||
<td>${d.catatan || "-"}</td>
|
||
</tr>`;
|
||
});
|
||
|
||
html += `</tbody></table>`;
|
||
}
|
||
|
||
/* =========================
|
||
TABEL EVENT
|
||
========================= */
|
||
if(event.length){
|
||
html += `
|
||
<h6 class="mt-4 text-primary">📌 Tabel Kegiatan Event</h6>
|
||
<table class="table table-sm table-report">
|
||
<thead class="table-primary">
|
||
<tr>
|
||
<th>Tanggal</th>
|
||
<th>Shift</th>
|
||
<th>Nama Event</th>
|
||
<th>Lokasi</th>
|
||
<th>Keterangan</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>`;
|
||
|
||
event.forEach(d=>{
|
||
html += `
|
||
<tr>
|
||
<td>${formatTanggalID(d.tanggal)}</td>
|
||
<td>${d.shift || "-"}</td>
|
||
<td>${d.namaEvent || "-"}</td>
|
||
<td>${d.lokasiEvent || d.wilayah || "-"}</td>
|
||
<td>${d.keterangan || "-"}</td>
|
||
</tr>`;
|
||
});
|
||
|
||
html += `</tbody></table>`;
|
||
}
|
||
|
||
rekapMingguan.innerHTML = html;
|
||
}
|
||
|
||
function tampilkanRekapBulanan(){
|
||
|
||
const bulanVal = filterBulan.value;
|
||
if(!bulanVal){
|
||
alert("Pilih bulan terlebih dahulu");
|
||
return;
|
||
}
|
||
|
||
const [tahun, bulan] = bulanVal.split("-");
|
||
|
||
const db = JSON.parse(localStorage.getItem("ambulans2026") || "[]");
|
||
const kendalaDB = JSON.parse(localStorage.getItem("kendalaShift") || "[]");
|
||
|
||
const data = db.filter(d =>
|
||
d.tanggal && d.tanggal.startsWith(`${tahun}-${bulan}`)
|
||
);
|
||
|
||
const kendala = kendalaDB.filter(k =>
|
||
k.tanggal && k.tanggal.startsWith(`${tahun}-${bulan}`)
|
||
);
|
||
|
||
if(!data.length){
|
||
rekapBulanan.innerHTML = `
|
||
<div class="alert alert-warning">
|
||
Tidak terdapat data laporan pada bulan ini.
|
||
</div>`;
|
||
return;
|
||
}
|
||
|
||
/* =========================
|
||
KELOMPOK DATA
|
||
========================= */
|
||
const pelayanan = data.filter(d => d.kategori === "Pelayanan");
|
||
const aktivitas = data.filter(d => d.kategori === "Aktivitas");
|
||
const event = data.filter(d => d.kategori === "Event");
|
||
const batal = data.filter(d => d.kategori === "Batal");
|
||
|
||
const trueEmergensi = data.filter(d => d.emergency === "true");
|
||
const nonEmergensi = data.filter(d => d.emergency === "false");
|
||
const tidakTerlayani = data.filter(d => d.kategori === "Batal");
|
||
|
||
/* =========================
|
||
NARASI BULANAN RESMI
|
||
========================= */
|
||
let html = `
|
||
<div class="alert alert-info">
|
||
<strong>Narasi Laporan Bulanan Ambulans 119</strong><br><br>
|
||
Berdasarkan rekapitulasi laporan Ambulans 119
|
||
selama bulan <strong>${bulan}-${tahun}</strong>,
|
||
tercatat sebanyak <strong>${data.length}</strong> kegiatan dengan rincian:
|
||
<ul class="mb-0">
|
||
<li>Pelayanan ambulans: <strong>${pelayanan.length}</strong> kegiatan</li>
|
||
<li>Aktivitas operasional/internal: <strong>${aktivitas.length}</strong> kegiatan</li>
|
||
<li>Tidak terlayani / batal: <strong>${tidakTerlayani.length}</strong> kegiatan</li>
|
||
<li>Standby event: <strong>${event.length}</strong> kegiatan</li>
|
||
<li>
|
||
Tercatat sebanyak <strong>${trueEmergensi.length}</strong> kasus
|
||
<strong>True Emergency</strong> dan
|
||
<strong>${nonEmergensi.length}</strong> kasus
|
||
<strong>Non Emergency</strong>.
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
`;
|
||
|
||
|
||
/* =========================
|
||
TABEL PELAYANAN & AKTIVITAS
|
||
========================= */
|
||
const utama = [...pelayanan, ...aktivitas];
|
||
|
||
if(utama.length){
|
||
html += `
|
||
<h6 class="mt-4">🚑 Tabel Pelayanan & Aktivitas</h6>
|
||
<table class="table table-sm table-report">
|
||
<thead class="table-light">
|
||
<tr>
|
||
<th>Tanggal</th>
|
||
<th>Shift</th>
|
||
<th>Kategori</th>
|
||
<th>Jenis Layanan</th>
|
||
<th>Status Kegawatan</th>
|
||
<th>Wilayah</th>
|
||
<th>Tujuan</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>`;
|
||
|
||
utama.forEach(d => {
|
||
html += `
|
||
<tr>
|
||
<td>${formatTanggalID(d.tanggal)}</td>
|
||
<td>${d.shift || "-"}</td>
|
||
<td>${d.kategori}</td>
|
||
<td>${JENIS_LABEL[d.jenis] || d.jenis || "-"}</td>
|
||
<td class="text-center">
|
||
${
|
||
d.emergency === "true"
|
||
? '<span class="badge bg-danger">True Emergency</span>'
|
||
: d.emergency === "false"
|
||
? '<span class="badge bg-success">Non Emergency</span>'
|
||
: '-'
|
||
}
|
||
</td>
|
||
<td>${d.wilayah || "-"}</td>
|
||
<td>${d.tujuan || "-"}</td>
|
||
</tr>`;
|
||
});
|
||
|
||
|
||
html += `</tbody></table>`;
|
||
}
|
||
|
||
/* =========================
|
||
TABEL TIDAK TERLAYANI / BATAL
|
||
========================= */
|
||
if(tidakTerlayani.length){
|
||
html += `
|
||
<h6 class="mt-4 text-danger">❌ Tabel Kegiatan Tidak Terlayani</h6>
|
||
<table class="table table-sm table-report">
|
||
<thead class="table-danger">
|
||
<tr>
|
||
<th>Tanggal</th>
|
||
<th>Shift</th>
|
||
<th>Alasan</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
`;
|
||
|
||
tidakTerlayani.forEach(d=>{
|
||
html += `
|
||
<tr>
|
||
<td>${formatTanggalID(d.tanggal)}</td>
|
||
<td>${d.shift || "-"}</td>
|
||
<td>${d.catatan || "-"}</td>
|
||
</tr>
|
||
`;
|
||
});
|
||
|
||
html += `</tbody></table>`;
|
||
}
|
||
|
||
/* =========================
|
||
TABEL EVENT
|
||
========================= */
|
||
if(event.length){
|
||
html += `
|
||
<h6 class="mt-4 text-primary">📌 Tabel Kegiatan Event</h6>
|
||
<table class="table table-bordered table-sm">
|
||
<thead class="table-primary">
|
||
<tr>
|
||
<th>Tanggal</th>
|
||
<th>Shift</th>
|
||
<th>Nama Event</th>
|
||
<th>Lokasi</th>
|
||
<th>Keterangan</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>`;
|
||
|
||
event.forEach(d=>{
|
||
html += `
|
||
<tr>
|
||
<td>${formatTanggalID(d.tanggal)}</td>
|
||
<td>${d.shift || "-"}</td>
|
||
<td>${d.namaEvent || "-"}</td>
|
||
<td>${d.lokasiEvent || d.wilayah || "-"}</td>
|
||
<td>${d.keterangan || "-"}</td>
|
||
</tr>`;
|
||
});
|
||
|
||
html += `</tbody></table>`;
|
||
}
|
||
|
||
/* =========================
|
||
KENDALA BULANAN (DI BAWAH EVENT)
|
||
========================= */
|
||
if(kendala.length){
|
||
html += `
|
||
<h5 class="mt-4 text-danger">⚠️ Kendala Operasional Bulanan</h5>
|
||
<table class="table table-bordered table-sm">
|
||
<thead class="table-danger">
|
||
<tr>
|
||
<th style="width:20%">Tanggal</th>
|
||
<th style="width:15%">Shift</th>
|
||
<th>Uraian Kendala</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
${kendala.map(k => `
|
||
<tr>
|
||
<td>${formatTanggalID(k.tanggal)}</td>
|
||
<td>${k.shift || "-"}</td>
|
||
<td>${k.kendala || k.uraian || k.keterangan || k.catatan || "-"}</td>
|
||
</tr>
|
||
`).join("")}
|
||
</tbody>
|
||
</table>
|
||
`;
|
||
} else {
|
||
html += `
|
||
<div class="alert alert-success mt-3">
|
||
✅ Tidak terdapat kendala operasional pada bulan ini.
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
|
||
rekapBulanan.innerHTML = html;
|
||
|
||
console.log("DB Kendala (SEMUA):", kendalaDB);
|
||
console.log("Kendala Bulanan (FILTER):", kendala);
|
||
}
|
||
|
||
function namaBulanID(bulan){
|
||
const list = [
|
||
"Januari","Februari","Maret","April","Mei","Juni",
|
||
"Juli","Agustus","September","Oktober","November","Desember"
|
||
];
|
||
return list[bulan];
|
||
}
|
||
|
||
function exportPDFBulanan(){
|
||
|
||
const { jsPDF } = window.jspdf;
|
||
|
||
const doc = new jsPDF({
|
||
orientation: "landscape",
|
||
unit: "mm",
|
||
format: "a4"
|
||
});
|
||
|
||
const PAGE_WIDTH = doc.internal.pageSize.getWidth();
|
||
const PAGE_HEIGHT = doc.internal.pageSize.getHeight();
|
||
const MARGIN = 10; // 1 cm
|
||
|
||
let y = 12;
|
||
|
||
/* =========================
|
||
JUDUL DINAMIS BULAN
|
||
========================= */
|
||
const bulanVal = document.getElementById("filterBulan")?.value;
|
||
|
||
let namaBulan = "-";
|
||
let tahun = "2026";
|
||
|
||
if(bulanVal){
|
||
const [yy, mm] = bulanVal.split("-");
|
||
namaBulan = namaBulanID(parseInt(mm,10)-1);
|
||
tahun = yy;
|
||
}
|
||
|
||
doc.setFont("helvetica","bold");
|
||
doc.setFontSize(13);
|
||
doc.text(
|
||
`LAPORAN BULANAN KEGIATAN PELAYANAN AMBULANS 119`,
|
||
PAGE_WIDTH/2,
|
||
y,
|
||
{ align:"center" }
|
||
);
|
||
|
||
y += 6;
|
||
doc.setFontSize(10);
|
||
doc.setFont("helvetica","normal");
|
||
doc.text(
|
||
`Bulan ${namaBulan} ${tahun}`,
|
||
PAGE_WIDTH/2,
|
||
y,
|
||
{ align:"center" }
|
||
);
|
||
|
||
/* =========================
|
||
HITUNG DATA BULANAN
|
||
========================= */
|
||
let db = JSON.parse(localStorage.getItem("ambulans2026") || "[]");
|
||
|
||
if(bulanVal){
|
||
db = db.filter(d => d.tanggal.startsWith(`${tahun}-${bulanVal.split("-")[1]}`));
|
||
}
|
||
|
||
const pelayanan = db.filter(d => d.kategori === "Pelayanan");
|
||
const aktivitas = db.filter(d => d.kategori === "Aktivitas");
|
||
const tidakTerlayani = db.filter(d => d.kategori === "Batal");
|
||
const event = db.filter(d => d.kategori === "Event");
|
||
|
||
const trueEmergensi = db.filter(d => d.emergency === "true");
|
||
const nonEmergensi = db.filter(d => d.emergency === "false");
|
||
|
||
/* =========================
|
||
NARASI BULANAN RESMI
|
||
========================= */
|
||
y += 6;
|
||
|
||
doc.setFontSize(8);
|
||
doc.setFont("helvetica","normal");
|
||
|
||
const narasi = [
|
||
`Berdasarkan rekapitulasi laporan kegiatan pelayanan Ambulans 119 selama bulan ${namaBulan} ${tahun}, tercatat sebanyak ${db.length} kegiatan dengan rincian sebagai berikut:`,
|
||
``,
|
||
`Pelayanan ambulans : ${pelayanan.length} kegiatan`,
|
||
`Aktivitas operasional/internal : ${aktivitas.length} kegiatan`,
|
||
`Tidak terlayani / batal : ${tidakTerlayani.length} kegiatan`,
|
||
`Standby event : ${event.length} kegiatan`,
|
||
``,
|
||
`Tercatat ${trueEmergensi.length} kasus True Emergency dan ${nonEmergensi.length} kasus Non Emergency selama periode tersebut.`
|
||
];
|
||
|
||
doc.text(
|
||
narasi,
|
||
MARGIN,
|
||
y,
|
||
{ lineHeightFactor: 1.45 }
|
||
);
|
||
|
||
y += narasi.length * 4 + 6;
|
||
|
||
|
||
/* =========================
|
||
AMBIL SEMUA TABEL
|
||
========================= */
|
||
const tables = document.querySelectorAll("#rekapBulanan table");
|
||
|
||
const rowsUtama = [];
|
||
const rowsBatal = [];
|
||
const rowsEvent = [];
|
||
|
||
doc.setFont("helvetica","bold");
|
||
doc.setFontSize(8);
|
||
y += 4;
|
||
|
||
/* =========================
|
||
TABEL UTAMA
|
||
========================= */
|
||
tables[0]?.querySelectorAll("tbody tr").forEach(tr=>{
|
||
const c = [...tr.children].map(td => td.innerText.trim());
|
||
rowsUtama.push([
|
||
c[0]||"-", c[1]||"-", c[2]||"-",
|
||
c[3]||"-", c[4]||"-", c[5]||"-", c[6]||"-"
|
||
]);
|
||
});
|
||
|
||
/* =========================
|
||
TABEL BATAL
|
||
========================= */
|
||
tables[1]?.querySelectorAll("tbody tr").forEach(tr=>{
|
||
const c = [...tr.children].map(td => td.innerText.trim());
|
||
rowsBatal.push([
|
||
c[0]||"-", c[1]||"-", c[2]||"-"
|
||
]);
|
||
});
|
||
|
||
/* =========================
|
||
TABEL EVENT
|
||
========================= */
|
||
tables[2]?.querySelectorAll("tbody tr").forEach(tr=>{
|
||
const c = [...tr.children].map(td => td.innerText.trim());
|
||
rowsEvent.push([
|
||
c[0]||"-", c[1]||"-", c[2]||"-", c[3]||"-", c[4]||"-"
|
||
]);
|
||
});
|
||
|
||
/* =========================
|
||
KOLOM KENDALA
|
||
========================= */
|
||
const rowsKendala = [];
|
||
|
||
tables[3]?.querySelectorAll("tbody tr").forEach(tr=>{
|
||
const c = [...tr.children].map(td => td.innerText.trim());
|
||
rowsKendala.push([
|
||
c[0] || "-", // Tanggal
|
||
c[1] || "-", // Shift
|
||
c[2] || "-" // Keterangan Kendala
|
||
]);
|
||
});
|
||
|
||
/* =========================
|
||
CETAK TABEL UTAMA
|
||
========================= */
|
||
doc.setFont("helvetica","bold");
|
||
doc.setFontSize(7);
|
||
doc.text("A. TABEL PELAYANAN & AKTIVITAS", MARGIN, y);
|
||
y += 3;
|
||
|
||
doc.autoTable({
|
||
startY: y,
|
||
head: [[
|
||
"Tanggal","Shift","Kategori","Jenis Layanan",
|
||
"Status","Wilayah","Tujuan"
|
||
]],
|
||
body: rowsUtama,
|
||
theme: "grid",
|
||
tableWidth: "wrap",
|
||
styles:{
|
||
fontSize: 6,
|
||
cellPadding: 1.8,
|
||
valign: "top",
|
||
overflow: "linebreak"
|
||
},
|
||
headStyles:{
|
||
fillColor:[230,235,245],
|
||
textColor:20,
|
||
halign:"center"
|
||
},
|
||
columnStyles:{
|
||
0:{cellWidth:22},
|
||
1:{cellWidth:14},
|
||
2:{cellWidth:20},
|
||
3:{cellWidth:45},
|
||
4:{cellWidth:26},
|
||
5:{cellWidth:22},
|
||
6:{cellWidth:88} // ⬅ disesuaikan
|
||
},
|
||
margin:{ left:MARGIN, right:MARGIN }
|
||
});
|
||
|
||
y = doc.lastAutoTable.finalY + 6;
|
||
|
||
/* =========================
|
||
CETAK TABEL BATAL
|
||
========================= */
|
||
if(rowsBatal.length){
|
||
doc.setFont("helvetica","bold");
|
||
doc.setFontSize(7);
|
||
doc.text("B. KEGIATAN TIDAK TERLAYANI / BATAL", MARGIN, y);
|
||
y += 3;
|
||
|
||
doc.autoTable({
|
||
startY: y,
|
||
head: [["Tanggal","Shift","Alasan"]],
|
||
body: rowsBatal,
|
||
theme: "grid",
|
||
tableWidth: "wrap",
|
||
styles:{
|
||
fontSize:6,
|
||
cellPadding:2,
|
||
overflow:"linebreak"
|
||
},
|
||
headStyles:{ fillColor:[255,220,220] },
|
||
columnStyles:{
|
||
0:{cellWidth:30},
|
||
1:{cellWidth:20},
|
||
2:{cellWidth:210} // ⬅ TURUN dari 220
|
||
},
|
||
margin:{ left:MARGIN, right:MARGIN }
|
||
});
|
||
|
||
y = doc.lastAutoTable.finalY + 6;
|
||
}
|
||
|
||
/* =========================
|
||
CETAK TABEL EVENT
|
||
========================= */
|
||
if(rowsEvent.length){
|
||
doc.setFont("helvetica","bold");
|
||
doc.setFontSize(7);
|
||
doc.text("C. KEGIATAN EVENT", MARGIN, y);
|
||
y += 3;
|
||
|
||
doc.autoTable({
|
||
startY: y,
|
||
head: [["Tanggal","Shift","Nama Event","Lokasi","Keterangan"]],
|
||
body: rowsEvent,
|
||
theme: "grid",
|
||
tableWidth: "wrap",
|
||
styles:{
|
||
fontSize:6,
|
||
cellPadding:2,
|
||
overflow:"linebreak"
|
||
},
|
||
headStyles:{ fillColor:[220,235,255] },
|
||
columnStyles:{
|
||
0:{cellWidth:22},
|
||
1:{cellWidth:18},
|
||
2:{cellWidth:60},
|
||
3:{cellWidth:55},
|
||
4:{cellWidth:122} // ⬅ aman & panjang masih kebaca
|
||
},
|
||
margin:{ left:MARGIN, right:MARGIN }
|
||
});
|
||
|
||
y = doc.lastAutoTable.finalY + 6;
|
||
}
|
||
|
||
/* =========================
|
||
CETAK KENDALA BULANAN
|
||
========================= */
|
||
if(rowsKendala.length){
|
||
doc.setFont("helvetica","bold");
|
||
doc.setFontSize(7);
|
||
doc.text("D. KENDALA BULANAN", MARGIN, y);
|
||
y += 3;
|
||
|
||
doc.autoTable({
|
||
startY: y,
|
||
head: [["Tanggal","Shift","Keterangan Kendala"]],
|
||
body: rowsKendala,
|
||
theme: "grid",
|
||
tableWidth: "wrap",
|
||
styles:{
|
||
fontSize:6,
|
||
cellPadding:2,
|
||
overflow:"linebreak",
|
||
valign:"top"
|
||
},
|
||
headStyles:{
|
||
fillColor:[255,235,200],
|
||
textColor:20,
|
||
halign:"center"
|
||
},
|
||
columnStyles:{
|
||
0:{cellWidth:30},
|
||
1:{cellWidth:20},
|
||
2:{cellWidth:220} // aman landscape
|
||
},
|
||
margin:{ left:MARGIN, right:MARGIN }
|
||
});
|
||
|
||
y = doc.lastAutoTable.finalY + 6;
|
||
}
|
||
|
||
/* =========================
|
||
FOOTER SEMUA HALAMAN
|
||
========================= */
|
||
const total = doc.getNumberOfPages();
|
||
|
||
for(let i=1;i<=total;i++){
|
||
doc.setPage(i);
|
||
doc.setFontSize(7);
|
||
doc.setTextColor(120);
|
||
|
||
doc.text(
|
||
"Dokumen Resmi SiLARAS 119 RSSA - Sistem Laporan Aktivitas Respon Ambulans 119 RSSA",
|
||
MARGIN,
|
||
PAGE_HEIGHT - 6
|
||
);
|
||
|
||
doc.text(
|
||
`Halaman ${i} dari ${total}`,
|
||
PAGE_WIDTH - MARGIN,
|
||
PAGE_HEIGHT - 6,
|
||
{align:"right"}
|
||
);
|
||
}
|
||
|
||
doc.save("Rekap_Bulanan_Ambulans_119.pdf");
|
||
}
|
||
|
||
|
||
/* =========================
|
||
KONFIGURASI ADMIN
|
||
========================= */
|
||
const ADMIN_PASSWORD = "119admin";
|
||
|
||
/* =========================
|
||
EXPORT DATA JSON
|
||
========================= */
|
||
function exportData(){
|
||
const data = localStorage.getItem("ambulans2026");
|
||
if(!data){
|
||
alert("Tidak ada data untuk diexport");
|
||
return;
|
||
}
|
||
|
||
const now = new Date();
|
||
const tanggal = now.toISOString().split("T")[0]; // YYYY-MM-DD
|
||
|
||
const namaFile = `ambulans119_2026_export_${tanggal}.json`;
|
||
|
||
const blob = new Blob([data], { type: "application/json" });
|
||
const url = URL.createObjectURL(blob);
|
||
|
||
const a = document.createElement("a");
|
||
a.href = url;
|
||
a.download = namaFile;
|
||
a.click();
|
||
|
||
URL.revokeObjectURL(url);
|
||
}
|
||
|
||
|
||
/* =========================
|
||
IMPORT DATA JSON
|
||
========================= */
|
||
function importData(){
|
||
const fileInput = document.getElementById("importFile");
|
||
const file = fileInput.files[0];
|
||
|
||
if(!file){
|
||
alert("Pilih file JSON terlebih dahulu");
|
||
return;
|
||
}
|
||
|
||
const reader = new FileReader();
|
||
reader.onload = e => {
|
||
try{
|
||
const data = JSON.parse(e.target.result);
|
||
if(!Array.isArray(data)) throw "Format tidak valid";
|
||
|
||
localStorage.setItem("ambulans2026", JSON.stringify(data));
|
||
alert("Data berhasil diimport");
|
||
location.reload();
|
||
}catch(err){
|
||
alert("File JSON tidak valid");
|
||
}
|
||
};
|
||
reader.readAsText(file);
|
||
}
|
||
|
||
/* =========================
|
||
AUTH ADMIN
|
||
========================= */
|
||
function authExport(){
|
||
const pass = prompt("Masukkan password admin:");
|
||
if(pass !== ADMIN_PASSWORD){
|
||
alert("Password salah. Akses ditolak.");
|
||
return;
|
||
}
|
||
exportData();
|
||
}
|
||
|
||
function authImport(){
|
||
const pass = prompt("Masukkan password admin:");
|
||
if(pass !== ADMIN_PASSWORD){
|
||
alert("Password salah. Akses ditolak.");
|
||
return;
|
||
}
|
||
importData();
|
||
}
|
||
// ================= GLOBAL =================
|
||
let armadaTanggal = "";
|
||
let armadaShift = "";
|
||
|
||
// ================= UPDATE VISUAL OXYGEN =================
|
||
function updateOxyVisual(input){
|
||
|
||
let val = parseInt(input.value || 0);
|
||
|
||
// BATAS NILAI
|
||
if(val < 0) val = 0;
|
||
if(val > 150) val = 150;
|
||
|
||
const fillId = input.dataset.fill;
|
||
const textId = input.dataset.text;
|
||
const statusId = input.dataset.status;
|
||
|
||
const fill = document.getElementById(fillId);
|
||
const text = document.getElementById(textId);
|
||
const status = document.getElementById(statusId);
|
||
|
||
// PERSENTASE TABUNG
|
||
let persen = (val / 150) * 100;
|
||
|
||
// RESET
|
||
fill.style.height = "0%";
|
||
fill.style.background = "#dfe6e9";
|
||
|
||
// HAPUS ANIMASI
|
||
fill.classList.remove("oxy-critical");
|
||
|
||
// ================= LEVEL =================
|
||
|
||
// 0 - 20 = HABIS
|
||
if(val <= 20){
|
||
|
||
fill.style.height = persen + "%";
|
||
|
||
fill.style.background =
|
||
"linear-gradient(to top,#6c757d,#adb5bd)";
|
||
|
||
status.innerHTML = "⚫ HABIS";
|
||
|
||
status.className =
|
||
"oxy-status oxy-gray";
|
||
|
||
fill.classList.add("oxy-critical");
|
||
}
|
||
|
||
// 21 - 40 = LOW
|
||
else if(val <= 40){
|
||
|
||
fill.style.height = persen + "%";
|
||
|
||
fill.style.background =
|
||
"linear-gradient(to top,#c0392b,#ff7675)";
|
||
|
||
status.innerHTML = "🔴 LOW";
|
||
|
||
status.className =
|
||
"oxy-status oxy-red";
|
||
|
||
fill.classList.add("oxy-critical");
|
||
}
|
||
|
||
// 41 - 80 = MID
|
||
else if(val <= 80){
|
||
|
||
fill.style.height = persen + "%";
|
||
|
||
fill.style.background =
|
||
"linear-gradient(to top,#f39c12,#ffe066)";
|
||
|
||
status.innerHTML = "🟡 MID";
|
||
|
||
status.className =
|
||
"oxy-status oxy-yellow";
|
||
}
|
||
|
||
// 81 - 150 = FULL
|
||
else{
|
||
|
||
fill.style.height = persen + "%";
|
||
|
||
fill.style.background =
|
||
"linear-gradient(to top,#16a085,#2ecc71)";
|
||
|
||
status.innerHTML = "🟢 FULL";
|
||
|
||
status.className =
|
||
"oxy-status oxy-green";
|
||
}
|
||
|
||
// ================= TEXT =================
|
||
text.innerHTML = val + " Kgf/cm³";
|
||
}
|
||
|
||
// ================= INIT OXYGEN =================
|
||
function initOxy(){
|
||
|
||
document.querySelectorAll(".oxy-knob").forEach(el => {
|
||
|
||
updateOxyVisual(el);
|
||
|
||
el.addEventListener("input", function(){
|
||
|
||
updateOxyVisual(this);
|
||
|
||
});
|
||
|
||
});
|
||
}
|
||
|
||
// ================= OPEN MODAL =================
|
||
function openModalArmada(tgl, shift){
|
||
|
||
armadaTanggal = tgl;
|
||
armadaShift = shift;
|
||
|
||
const key = "armada_" + tgl + "_" + shift;
|
||
|
||
const data = JSON.parse(
|
||
localStorage.getItem(key) || "{}"
|
||
);
|
||
|
||
for(let i = 1; i <= 3; i++){
|
||
|
||
// ================= BBM =================
|
||
const bbm = document.getElementById("bbm_"+i);
|
||
|
||
if(bbm){
|
||
bbm.value = data["bbm_"+i] || "4";
|
||
|
||
// 🔥 IMPORTANT: sync jarum saat modal dibuka
|
||
updateFuelSemi(bbm);
|
||
}
|
||
|
||
// ================= KM =================
|
||
document.getElementById("km_"+i).value =
|
||
data["km_"+i] || "";
|
||
|
||
// ================= STATUS =================
|
||
document.getElementById("status_"+i).value =
|
||
data["status_"+i] || "";
|
||
|
||
// ================= OXYGEN =================
|
||
for(let j = 1; j <= 3; j++){
|
||
|
||
const oxy =
|
||
document.getElementById("oxy"+i+"_"+j);
|
||
|
||
if(oxy){
|
||
|
||
oxy.value =
|
||
data["oxy"+i+"_"+j] || "150";
|
||
|
||
updateOxyVisual(oxy);
|
||
}
|
||
}
|
||
|
||
// ================= CADANGAN =================
|
||
document.getElementById("oxy"+i+"_cadangan").value =
|
||
data["oxy"+i+"_cadangan"] || "tersegel";
|
||
}
|
||
|
||
initOxy();
|
||
|
||
new bootstrap.Modal(
|
||
document.getElementById("modalArmada")
|
||
).show();
|
||
}
|
||
|
||
function simpanArmada(){
|
||
|
||
const data = {};
|
||
|
||
for(let i = 1; i <= 3; i++){
|
||
|
||
// ================= BBM =================
|
||
const bbm = document.getElementById("bbm_"+i);
|
||
data["bbm_"+i] = bbm ? bbm.value : "0";
|
||
|
||
// ================= KM =================
|
||
const km = document.getElementById("km_"+i);
|
||
data["km_"+i] = km ? km.value : "";
|
||
|
||
// ================= STATUS =================
|
||
const status = document.getElementById("status_"+i);
|
||
data["status_"+i] = status ? status.value : "";
|
||
|
||
// ================= OXYGEN =================
|
||
for(let j = 1; j <= 3; j++){
|
||
|
||
const oxy = document.getElementById("oxy"+i+"_"+j);
|
||
|
||
data["oxy"+i+"_"+j] = oxy ? oxy.value : "0";
|
||
}
|
||
|
||
// ================= CADANGAN =================
|
||
const cad = document.getElementById("oxy"+i+"_cadangan");
|
||
|
||
data["oxy"+i+"_cadangan"] = cad ? cad.value : "tersegel";
|
||
}
|
||
|
||
const key = "armada_" + armadaTanggal + "_" + armadaShift;
|
||
|
||
localStorage.setItem(key, JSON.stringify(data));
|
||
|
||
// close modal
|
||
const modalEl = document.getElementById("modalArmada");
|
||
const modalInstance = bootstrap.Modal.getInstance(modalEl);
|
||
|
||
if(modalInstance){
|
||
modalInstance.hide();
|
||
}
|
||
|
||
tampil();
|
||
}
|
||
// ================= STATUS BADGE =================
|
||
function badgeStatus(status){
|
||
|
||
if(!status){
|
||
|
||
return `
|
||
<span class="badge bg-secondary">
|
||
Belum diisi
|
||
</span>
|
||
`;
|
||
}
|
||
|
||
status = status.toLowerCase();
|
||
|
||
switch(status){
|
||
|
||
case "siap":
|
||
|
||
return `
|
||
<span class="badge bg-success">
|
||
🟢 Siap Operasional
|
||
</span>
|
||
`;
|
||
|
||
case "maintenance":
|
||
|
||
return `
|
||
<span class="badge bg-warning text-dark">
|
||
🟡 Dalam Perawatan
|
||
</span>
|
||
`;
|
||
|
||
case "tidak_layak":
|
||
|
||
return `
|
||
<span class="badge bg-danger">
|
||
🔴 Tidak Laik Operasional
|
||
</span>
|
||
`;
|
||
|
||
default:
|
||
|
||
return `
|
||
<span class="badge bg-secondary">
|
||
${status}
|
||
</span>
|
||
`;
|
||
}
|
||
}
|
||
function loadArmada(tgl, shift){
|
||
|
||
const key = "armada_" + tgl + "_" + shift;
|
||
|
||
const data = JSON.parse(
|
||
localStorage.getItem(key) || "{}"
|
||
);
|
||
|
||
for(let i = 1; i <= 3; i++){
|
||
|
||
// ================= BBM =================
|
||
const bbmInput = document.getElementById("bbm_"+i);
|
||
|
||
if(bbmInput){
|
||
bbmInput.value = data["bbm_"+i] || "4";
|
||
|
||
// 🔥 IMPORTANT: update jarum langsung
|
||
updateFuelSemi(bbmInput);
|
||
}
|
||
|
||
// ================= KM =================
|
||
document.getElementById("km_"+i).value =
|
||
data["km_"+i] || "";
|
||
|
||
// ================= STATUS =================
|
||
document.getElementById("status_"+i).value =
|
||
data["status_"+i] || "";
|
||
|
||
// ================= OXYGEN =================
|
||
for(let j = 1; j <= 3; j++){
|
||
|
||
const oxy =
|
||
document.getElementById("oxy"+i+"_"+j);
|
||
|
||
if(oxy){
|
||
|
||
oxy.value =
|
||
data["oxy"+i+"_"+j] || "150";
|
||
|
||
updateOxyVisual(oxy);
|
||
}
|
||
}
|
||
|
||
// ================= CADANGAN =================
|
||
document.getElementById("oxy"+i+"_cadangan").value =
|
||
data["oxy"+i+"_cadangan"] || "tersegel";
|
||
}
|
||
|
||
initOxy();
|
||
}
|
||
|
||
function updateFuelSemi(input){
|
||
|
||
let val = parseFloat(input.value || 0);
|
||
|
||
// clamp 0 - 4
|
||
val = Math.max(0, Math.min(4, val));
|
||
|
||
let id = input.id.split("_")[1];
|
||
|
||
// update angka
|
||
document.getElementById("fuel_val_" + id)
|
||
.innerText = val.toFixed(1);
|
||
|
||
let needle = document.getElementById("fuel_needle_" + id);
|
||
|
||
// ===== ANGLE SYSTEM (0=LEFT, 4=RIGHT) =====
|
||
let angle = 180 - (val * 45);
|
||
let rad = angle * Math.PI / 180;
|
||
|
||
// pivot center
|
||
let cx = 100;
|
||
let cy = 110;
|
||
|
||
// panjang jarum
|
||
let length = 80;
|
||
|
||
// endpoint jarum
|
||
let x = cx + length * Math.cos(rad);
|
||
let y = cy - length * Math.sin(rad);
|
||
|
||
// update needle
|
||
needle.setAttribute("x2", x);
|
||
needle.setAttribute("y2", y);
|
||
}
|
||
</script>
|
||
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script>
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf-autotable/3.5.29/jspdf.plugin.autotable.min.js"></script>
|
||
|
||
</body>
|
||
</html> |