Files
silaras-rssa/public/laporan/LAPORANKAJAGA.html
T

4463 lines
114 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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>